Skip to content

fix(ui): namespace tailwind utilities under dg: prefix to stop host-page collisions#32

Merged
lukeocodes merged 1 commit intomainfrom
fix/scope-tailwind-utilities-with-dg-prefix
May 7, 2026
Merged

fix(ui): namespace tailwind utilities under dg: prefix to stop host-page collisions#32
lukeocodes merged 1 commit intomainfrom
fix/scope-tailwind-utilities-with-dg-prefix

Conversation

@lukeocodes
Copy link
Copy Markdown
Member

Why

deepgram/ui#27 scoped Tailwind preflight to [data-dg-agent] via tailwindcss-scoped-preflight, but utility classes still ship unprefixed. When the bundle loads on a host page that also uses Tailwind, source order makes the bundle's .prose, .flex, .container, .max-w-none etc rules win and visibly break host layout. Reproduced on the deepgram-docs preview where .prose { max-width: 65ch } collapses content the moment the widget mounts. 42 utility-class collisions counted against Fern's HTML alone; more on arbitrary customer sites.

tailwindcss-scoped-preflight only handles preflight (*, ::before, ::after). Utilities and the typography plugin's .prose rules need a different mechanism.

What changed

  • packages/ui/src/styles.css: switched to @import "tailwindcss" prefix(dg) (Tailwind v4 prefix syntax). Replaced the granular theme/utilities imports because prefix applies cleanly to the full import. The scoped-preflight plugin still runs for the universal-selector reset.
  • All .tsx components: every Tailwind utility in className=, cn(...), and cva(...) strings now uses the dg: prefix. flex items-center gap-2 -> dg:flex dg:items-center dg:gap-2, hover:bg-accent -> dg:hover:bg-accent, data-[state=on]:bg-primary -> dg:data-[state=on]:bg-primary, [&>span]:line-clamp-1 -> dg:[&>span]:line-clamp-1. Custom .dg-* component classes (dg-btn, dg-panel, dg-fab) are unchanged - they're not Tailwind utilities.
  • defaultVariants keys in cva(...) calls ({ variant: "default" }) stay unprefixed - those are variant-name references, not class names.

Verification

before (0.1.1) after
Unprefixed Tailwind utility rules in bundle 42 collisions with Fern HTML, many more in general 0
Prefixed .dg\:* utility rules 0 353
Scoped :where([data-dg-agent], …) preflight rules 76 76 (unchanged)
Custom .dg-* component classes 33 33 (unchanged)
Unit tests 13/13 pass 13/13 pass
Bundle size 167.5 KB 188.9 KB (+21 KB, the prefixed utility rules)

The +21 KB is real generated CSS that was silently dropped before because Tailwind couldn't see prefix-less class usages with prefix mode on. With the prefix matching, all utility classes used by components now emit rules.

Expected after merge

  1. release-please opens chore(main): release ui 0.1.2 (patch bump under bump-patch-for-minor-pre-major).
  2. Merging that publishes @deepgram/ui@0.1.2 to npm.
  3. deepgram/agent needs a follow-up PR bumping @deepgram/ui to ^0.1.2 in packages/widget/package.json. That triggers @deepgram/agents-widget@0.1.5 -> CDN deploy with the prefixed bundle.
  4. Once cdn.deepgram.com/widgets/latest/widget.umd.js ships the prefixed bundle, the docs preview narrowing-on-mount bug goes away and customer sites embedding the widget stop having their layouts hijacked.

Refs

Note for downstream consumers

Anyone writing components that use @deepgram/ui Tailwind utilities directly (rare - most consumers use the React components without touching their classes) MUST use the dg: prefix:

// Before
<div className="flex items-center gap-2" />

// After
<div className="dg:flex dg:items-center dg:gap-2" />

This only applies to consumers running their own Tailwind through @deepgram/ui's styles. Consumers who just import <AgentMicrophoneButton /> etc are unaffected.

…age collisions

The 0.1.1 release scoped Tailwind preflight to [data-dg-agent] via
tailwindcss-scoped-preflight, but utility classes (.flex, .prose,
.container, .max-w-none, .gap-2, .px-3, .text-sm, .border, etc) and
component classes from the typography plugin (.prose) still ship
unprefixed. When the bundle loads on a host page that also uses
Tailwind, the bundle's class rules win on source order and visibly
break host layout. The most common symptom is .prose snapping content
to max-width: 65ch the moment the widget mounts.

Switching to `@import "tailwindcss" prefix(dg)` namespaces every
generated class under a dg: variant, so .flex becomes .dg\:flex,
.prose becomes .dg\:prose, .max-w-none becomes .dg\:max-w-none, and
so on. Class names in this package's components are updated to match
(class="dg:flex dg:items-center" rather than class="flex items-center").
The scoped preflight plugin keeps doing its job for the universal
selector reset.

After this change the bundle ships:
  - 0 unprefixed Tailwind utility class rules (was 42 collisions with
    Fern docs alone, more with arbitrary host pages)
  - 353 prefixed .dg\: utility rules
  - 76 scoped :where([data-dg-agent], …) preflight rules
  - 33 .dg-* custom component classes (unchanged)

Bundle size: 167.5 KB -> 188.9 KB (+21 KB) from the additional
generated CSS now that Tailwind sees the prefixed class usages and
emits rules for them. Previously these classes were silently dropped
because they weren't matching the prefix-less convention.

Refs:
- https://tailwindcss.com/docs/styling-with-utility-classes#using-a-prefix
- deepgram/agent#48 (the scoped preflight 0.1.1 work that this completes)
- deepgram/dx-stack#4 (will gain a "scoping utilities, not just preflight" gotcha)
@vercel
Copy link
Copy Markdown

vercel Bot commented May 7, 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 7, 2026 8:41am

Request Review

@lukeocodes lukeocodes merged commit 2b342b7 into main May 7, 2026
4 checks passed
@lukeocodes lukeocodes deleted the fix/scope-tailwind-utilities-with-dg-prefix branch May 7, 2026 08:43
lukeocodes added a commit to deepgram/agent that referenced this pull request May 7, 2026
…ies (#50)

## Why


[`@deepgram/ui@0.1.2`](https://www.npmjs.com/package/@deepgram/ui/v/0.1.2)
(just published from
[deepgram/ui#32](deepgram/ui#32)) namespaces
every Tailwind utility class under a `dg:` prefix. The previous 0.1.1
release scoped Tailwind preflight to `[data-dg-agent]` but left utility
classes (`.prose`, `.flex`, `.container`, `.max-w-none`, `.gap-2`,
`.px-3`, `.text-sm`, etc) unprefixed. When the widget loads on a host
page that also uses Tailwind, source order makes the bundle's class
rules win and hijack host layout. The visible symptom is `.prose {
max-width: 65ch }` snapping content narrow the moment the widget mounts.

Until the widget bumps its dependency, the published UMD on
`cdn.deepgram.com/widgets/` still ships unprefixed Tailwind utilities
that bleed onto every host site embedding the widget.

## What changed

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

## Verification

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

| | before (0.1.1) | after (0.1.2) |
|---|---|---|
| Unprefixed `.prose`, `.flex`, `.container`, `.max-w-none` rules | many
(42 collisions vs Fern) | **0** |
| Prefixed `.dg\:*` utility refs | 0 | **353** |
| Scoped `:where([data-dg-agent], …)` preflight rules | 76 | **76**
(unchanged) |
| Bundle size | 387.5 KB | **393.5 KB** (+6 KB) |

The +6 KB is real generated CSS that 0.1.1 was silently dropping because
Tailwind couldn't see prefix-less class usages with prefix mode on.

## Expected after merge

1. release-please opens `chore(main): release agents-widget 0.1.5`.
2. Merging that PR bumps `packages/widget/package.json` to 0.1.5 and
tags `agents-widget-v0.1.5`.
3. The npm publish workflow pushes `@deepgram/agents-widget@0.1.5` to
npm.
4. The CDN publish job uploads the new UMD bundle to:
- `https://cdn.deepgram.com/widgets/v0.1.5/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 prefixed bundle with no Tailwind
utility leaks.

## Refs

- [deepgram/ui#32](deepgram/ui#32) - the
prefix(dg) fix
- [#48](#48) - the
scoped preflight 0.1.1 work that this completes
-
[deepgram/deepgram-docs#777](deepgram/deepgram-docs#777)
- where the .prose narrowing was first observed
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