Skip to content

feat(apollo-wind): add shadow DOM CSS injection utilities#830

Draft
david-rios-uipath wants to merge 3 commits into
mainfrom
rios/shadow-dom-property-registration
Draft

feat(apollo-wind): add shadow DOM CSS injection utilities#830
david-rios-uipath wants to merge 3 commits into
mainfrom
rios/shadow-dom-property-registration

Conversation

@david-rios-uipath

@david-rios-uipath david-rios-uipath commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds reusable utilities for injecting apollo-wind Tailwind CSS into shadow DOM. Browsers silently ignore @property rules inside shadow root <style> elements, breaking Tailwind v4 composable utilities (border, shadow-*, ring-*, translate-*). Every shadow DOM consumer was reimplementing the same inject + register dance — this PR consolidates it.

See upstream discussion: tailwindlabs/tailwindcss#16772

Immediate consumer: @uipath/traceview — migrating shared components from MUI to apollo-wind (Radix), which portal inside a shadow root.

New exports

  • injectTailwindIntoShadowRoot(root, css) — injects a <style data-tailwind-inject> into the shadow root and registers @property rules at the document level. Idempotent. Returns the created HTMLStyleElement (or null if skipped) so callers can clean up on unmount.
  • hasApolloWindCss(root) — two-level detection: fast-path tag check + computed-style probes (grid/flex/hidden/relative). Deliberately avoids probing custom properties (they inherit across the shadow boundary and would false-positive).
  • TAILWIND_INJECT_ATTR — sentinel attribute constant (data-tailwind-inject).

Usage

import { injectTailwindIntoShadowRoot } from '@uipath/apollo-wind';
import css from '@uipath/apollo-react/canvas/styles/tailwind.canvas.css?raw';

// In a web component's connectedCallback:
const shadow = this.attachShadow({ mode: 'open' });
injectTailwindIntoShadowRoot(shadow, css);

Changes

  • New: src/lib/register-shadow-dom-properties.ts — internal helper that extracts @property rules via regex and injects into document.head. Idempotent, SSR-safe.
  • New: src/lib/shadow-dom-css.ts — the public API: injectTailwindIntoShadowRoot, hasApolloWindCss, TAILWIND_INJECT_ATTR. Calls registerCssPropertyRules internally.
  • New: Tests for both modules.
  • Updated: src/lib/index.ts and src/index.ts — export the public API.

Testing

  • pnpm --filter @uipath/apollo-wind test passes (923 tests)
  • pnpm --filter @uipath/apollo-wind build — new exports in dist/index.d.ts
  • Verified in traceview-ui Storybook: tooltips render fully styled inside shadow DOM

🤖 Generated with Claude Code

Copilot AI review requested due to automatic review settings June 16, 2026 16:10
@github-actions

github-actions Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

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

Project Deployment Review Updated (PT)
apollo-design 🟢 Ready Preview, Logs Jun 25, 2026, 12:19:04 PM
apollo-docs 🟢 Ready Preview, Logs Jun 25, 2026, 12:19:04 PM
apollo-landing 🟢 Ready Preview, Logs Jun 25, 2026, 12:19:04 PM
apollo-vertex 🟢 Ready Preview, Logs Jun 25, 2026, 12:19:04 PM

@github-actions

github-actions Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Dependency License Review

  • 1945 package(s) scanned
  • ✅ No license issues found
  • ⚠️ 2 package(s) excluded (see details below)
License distribution
License Packages
MIT 1715
ISC 89
Apache-2.0 55
BSD-3-Clause 27
BSD-2-Clause 23
BlueOak-1.0.0 8
MPL-2.0 4
MIT-0 3
CC0-1.0 3
MIT OR Apache-2.0 2
(MIT OR Apache-2.0) 2
Unlicense 2
LGPL-3.0-or-later 1
Python-2.0 1
CC-BY-4.0 1
(MPL-2.0 OR Apache-2.0) 1
Unknown 1
Artistic-2.0 1
(WTFPL OR MIT) 1
(BSD-2-Clause OR MIT OR Apache-2.0) 1
CC-BY-3.0 1
0BSD 1
(MIT OR CC0-1.0) 1
MIT AND ISC 1
Excluded packages
Package Version License Reason
@img/sharp-libvips-linux-x64 1.2.4 LGPL-3.0-or-later LGPL pre-built binary, not linked
khroma 2.1.0 Unknown MIT per GitHub repo, missing license field in package.json

Comment thread packages/apollo-wind/src/lib/register-shadow-dom-properties.ts Fixed

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Not ready to approve

The new helper claims idempotency but the unit tests don’t currently assert the idempotent behavior, leaving a key behavior unverified.

Pull request overview

Adds a new @uipath/apollo-wind utility to support Tailwind v4 inside Shadow DOM by extracting @property at-rules from a CSS string and registering them globally via a single marker <style> in document.head.

Changes:

  • Introduces registerCssPropertyRules(css) to extract Tailwind @property rules and inject them once at the document level.
  • Adds unit tests verifying extraction and filtering behavior.
  • Exports the new utility from both the internal lib barrel and the public package entry point.
File summaries
File Description
packages/apollo-wind/src/lib/register-shadow-dom-properties.ts Implements registerCssPropertyRules with regex-based extraction and idempotent DOM marker injection.
packages/apollo-wind/src/lib/register-shadow-dom-properties.test.ts Adds tests for extraction, filtering, and (needs adjustment) idempotency coverage.
packages/apollo-wind/src/lib/index.ts Re-exports the helper from the lib barrel.
packages/apollo-wind/src/index.ts Re-exports the helper from the public API.

Copilot's findings

  • Files reviewed: 4/4 changed files
  • Comments generated: 2

Note

Your feedback helps us improve the quality of this feature.
Please use 👍 or 👎 to tell us whether this assessment is correct.

Comment on lines +44 to +49
it('does nothing when CSS has no @property rules', () => {
registerCssPropertyRules('.flex { display: flex; }');

expect(document.querySelector(SELECTOR)).toBeNull();
});
});
Comment thread packages/apollo-wind/src/lib/register-shadow-dom-properties.ts Outdated
@david-rios-uipath david-rios-uipath added the dev-packages Adds dev package publishing on pushes to this PR label Jun 18, 2026
@github-actions

github-actions Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

📦 Dev Packages

Package Status Updated (PT)
@uipath/apollo-wind@2.24.0-pr830.cb7e86a 🟢 Published Jun 25, 2026, 12:19:06 PM

@github-actions

github-actions Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

📊 Coverage + size by package

Per-package coverage and bundle size on this PR. New-line coverage = of the source lines this PR adds or changes, the % hit by tests.

Package Coverage New-line coverage Packed (gzip) Unpacked vs main
@uipath/apollo-core 9.0% 43.82 MB 57.31 MB ±0
@uipath/apollo-react 34.7% 7.27 MB 27.58 MB ±0
@uipath/apollo-wind 39.9% 100.0% (40/40) 393.3 KB 2.55 MB +3.2 KB
@uipath/ap-chat 85.8% 43.41 MB 55.85 MB ±0

"Coverage" is each package's own coverage.include scope (e.g. apollo-core instruments only scripts/). "Packed"/"Unpacked" come from npm pack --dry-run and only cover built packages — "—" means not measured this run (package not affected / not built). "vs main" is the packed (gzipped) delta against the last successful main build (the package-sizes artifact from the Release workflow); "—" there means no main baseline was available this run. The baseline is main's latest build, not this PR's exact merge-base, so it includes any drift since the branch diverged. Packages with no vitest config are omitted.

Copilot AI review requested due to automatic review settings June 25, 2026 19:08
@david-rios-uipath david-rios-uipath force-pushed the rios/shadow-dom-property-registration branch from 9d18f76 to 074dd34 Compare June 25, 2026 19:08
@david-rios-uipath david-rios-uipath changed the title feat(apollo-wind): add registerCssPropertyRules for shadow DOM feat(apollo-wind): add shadow DOM CSS injection utilities Jun 25, 2026
Browsers silently ignore @Property rules inside shadow DOM <style>
elements, breaking Tailwind v4 composable utilities (border, shadow,
ring, translate). Every shadow DOM consumer was reimplementing the same
inject + register dance.

New exports:
- injectTailwindIntoShadowRoot(root, css) — injects stylesheet + registers @Property rules
- hasApolloWindCss(root) — two-level detection (tag check + computed-style probes)
- TAILWIND_INJECT_ATTR — sentinel attribute constant
Comment thread packages/apollo-wind/src/lib/shadow-dom-css.test.ts Fixed
@david-rios-uipath david-rios-uipath force-pushed the rios/shadow-dom-property-registration branch from 074dd34 to 93f0ca3 Compare June 25, 2026 19:10

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Comment on lines +32 to +50
export function hasApolloWindCss(root: Document | ShadowRoot): boolean {
if (root.querySelector(`style[${TAILWIND_INJECT_ATTR}]`)) {
return true;
}

const probeParent = root instanceof Document ? root.body : root;
const probe = document.createElement('div');
probeParent.appendChild(probe);

try {
const computed = getComputedStyle(probe);
return PROBES.every((p) => {
probe.className = p.className;
return computed.getPropertyValue(p.property) === p.expected;
});
} finally {
probe.remove();
}
}
Comment on lines +35 to +37
if (typeof document === 'undefined') return;
if (document.querySelector(`style[${MARKER}]`)) return;

Comment thread packages/apollo-wind/src/lib/shadow-dom-css.test.ts Outdated
Copilot AI review requested due to automatic review settings June 25, 2026 19:12
@david-rios-uipath david-rios-uipath force-pushed the rios/shadow-dom-property-registration branch from 4948996 to 72a6254 Compare June 25, 2026 19:13

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.

Comment on lines +37 to +46
const probeParent = root instanceof Document ? root.body : root;
const probe = document.createElement('div');
probeParent.appendChild(probe);

try {
const computed = getComputedStyle(probe);
return PROBES.every((p) => {
probe.className = p.className;
return computed.getPropertyValue(p.property) === p.expected;
});
Comment on lines +75 to +80
const style = document.createElement('style');
style.setAttribute(TAILWIND_INJECT_ATTR, '');
style.textContent = css;
root.prepend(style);
registerCssPropertyRules(css);
return style;
Comment on lines +33 to +44
export function registerCssPropertyRules(css: string): void {
if (typeof document === 'undefined') return;
if (document.querySelector(`style[${MARKER}]`)) return;

const rules = extractPropertyRules(css);
if (rules.length === 0) return;

const style = document.createElement('style');
style.setAttribute(MARKER, '');
style.textContent = rules.join('\n');
document.head.appendChild(style);
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both points are intentional:

  1. Single-call idempotency: There's one CSS bundle (tailwind.canvas.css). This function is @internal — only injectTailwindIntoShadowRoot calls it, always with the same CSS. Merge logic would be dead code.

  2. Global document: The consumer (TraceView web component) always runs in the host page's document, never in iframes. Adding an optional Document parameter complicates the API for a nonexistent scenario.

Comment thread packages/apollo-wind/src/lib/shadow-dom-css.test.ts Outdated
Comment thread packages/apollo-wind/src/lib/shadow-dom-css.test.ts
Copilot AI review requested due to automatic review settings June 25, 2026 19:16
@david-rios-uipath david-rios-uipath force-pushed the rios/shadow-dom-property-registration branch from 0f0885d to 96d87ea Compare June 25, 2026 19:18

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Comment on lines +37 to +39
const probeParent = root instanceof Document ? root.body : root;
const probe = document.createElement('div');
probeParent.appendChild(probe);
Comment on lines +33 to +44
export function registerCssPropertyRules(css: string): void {
if (typeof document === 'undefined') return;
if (document.querySelector(`style[${MARKER}]`)) return;

const rules = extractPropertyRules(css);
if (rules.length === 0) return;

const style = document.createElement('style');
style.setAttribute(MARKER, '');
style.textContent = rules.join('\n');
document.head.appendChild(style);
}
Comment on lines +29 to +33
afterEach(() => {
document.querySelectorAll(SELECTOR).forEach((el) => { el.remove(); });
document.querySelectorAll(PROPERTY_SELECTOR).forEach((el) => { el.remove(); });
document.querySelectorAll('div').forEach((el) => { el.parentNode?.removeChild(el); });
});
Copilot AI review requested due to automatic review settings June 25, 2026 19:20

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Comment on lines +37 to +39
const probeParent = root instanceof Document ? root.body : root;
const probe = document.createElement('div');
probeParent.appendChild(probe);
Comment on lines +42 to +46
const computed = getComputedStyle(probe);
return PROBES.every((p) => {
probe.className = p.className;
return computed.getPropertyValue(p.property) === p.expected;
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dev-packages Adds dev package publishing on pushes to this PR pkg:apollo-wind size:L 100-499 changed lines.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants