fix(css): scope ProseMirror CSS to prevent bleeding into host apps (SD-1850)#2134
fix(css): scope ProseMirror CSS to prevent bleeding into host apps (SD-1850)#2134
Conversation
…D-1850)
SuperDoc's CSS was leaking into host applications in three ways:
1. A bare `a { text-decoration: auto }` selector overrode ALL links on the
page, not just those inside the editor.
2. All `.ProseMirror` selectors (~55) were unscoped, affecting any
ProseMirror editor on the page — not just SuperDoc's hidden instance.
3. A `.sr-only` utility class collided with Bootstrap/Tailwind definitions.
This follows the CKEditor 5 model (the industry standard): scope all
content styles under a parent class. SuperDoc already did this for ~90%
of its CSS — this commit closes the remaining gaps.
Changes:
- Remove bare `a` element selector from global.css
- Scope all `.ProseMirror` and `.ProseMirror-*` selectors under
`.super-editor` in prosemirror.css
- Scope `.sd-*` selectors in extension CSS files (structured-content,
document-section, noderesizer, ai, page-number)
- Rename keyframes `aiTextAppear` → `superdoc-aiTextAppear` and
`ai-pulse` → `superdoc-ai-pulse` to avoid collisions
- Rename `.sr-only` → `.superdoc-sr-only` in DomPainter styles
- Add CSS bleed prevention lint test that structurally verifies no
unscoped selectors exist (catches future regressions)
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
There was a problem hiding this comment.
Pull request overview
This PR addresses CSS bleeding from SuperDoc into host applications by implementing comprehensive parent class scoping, following the CKEditor 5 model. The changes prevent three types of CSS conflicts: bare element selectors, unscoped ProseMirror selectors, and generic utility class name collisions.
Changes:
- Removed bare
aelement selector fromglobal.css(already covered by scoped.super-editor arule) - Scoped all ~55
.ProseMirrorand.ProseMirror-*selectors under.super-editorinprosemirror.css - Scoped all
.sd-*selectors in extension CSS files under.super-editor - Renamed
.sr-onlyto.superdoc-sr-onlyto prevent framework collisions - Namespaced AI-related keyframe animations with
superdoc-prefix - Added comprehensive structural lint tests to prevent future CSS bleed regressions
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
packages/super-editor/src/tests/css-no-bleed.test.js |
New structural lint test that validates no bare HTML selectors, all .ProseMirror selectors scoped, and no generic utility class collisions |
packages/super-editor/src/assets/styles/layout/global.css |
Removed unscoped a selector that was bleeding into host apps |
packages/super-editor/src/assets/styles/elements/prosemirror.css |
Scoped all ProseMirror selectors under .super-editor to prevent conflicts with other ProseMirror instances |
packages/super-editor/src/assets/styles/extensions/structured-content.css |
Scoped all .sd-structured-content* selectors under .super-editor |
packages/super-editor/src/assets/styles/extensions/noderesizer.css |
Scoped all node resizer selectors under .super-editor |
packages/super-editor/src/assets/styles/extensions/document-section.css |
Scoped all document section selectors under .super-editor |
packages/super-editor/src/assets/styles/elements/page-number.css |
Scoped page number selectors under .super-editor |
packages/super-editor/src/assets/styles/elements/ai.css |
Scoped AI selectors and namespaced keyframes (superdoc-aiTextAppear, superdoc-ai-pulse) |
packages/layout-engine/painters/dom/src/styles.ts |
Renamed .sr-only to .superdoc-sr-only to prevent Bootstrap/Tailwind collisions |
packages/layout-engine/painters/dom/src/renderer.ts |
Updated class name usage from sr-only to superdoc-sr-only |
packages/layout-engine/painters/dom/src/index.test.ts |
Updated test assertions to verify superdoc-sr-only class name |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -368,18 +371,18 @@ https://github.com/ProseMirror/prosemirror-tables/blob/master/demo/index.html | |||
| } | |||
| } | |||
There was a problem hiding this comment.
The keyframe animation ProseMirror-cursor-blink should be renamed to superdoc-ProseMirror-cursor-blink to prevent collisions with other ProseMirror instances on the page. Keyframe names are global and are not scoped by CSS parent selectors. This follows the same pattern used for the other keyframe animations in this PR (superdoc-aiTextAppear, superdoc-ai-pulse).
caio-pizzol
left a comment
There was a problem hiding this comment.
nice work scoping all the CSS
feel free to merge after addressing relevant comments
packages/super-editor/src/assets/styles/elements/prosemirror.css
Outdated
Show resolved
Hide resolved
- Rename ProseMirror-cursor-blink keyframe to superdoc-cursor-blink - Scope .toolbar-icon__icon--ai selectors under .super-editor - Add @Keyframes name lint test requiring superdoc-/sd- prefix - Remove .toolbar-icon from scopedPrefixes allowlist
The per-section measurement constraints change belongs to the SD-1962 table pagination fix, not the CSS scoping work. Reverting this file to match main; the change lives in tadeu/sd-1962-nested-table-pagination.
Problem
SuperDoc's CSS was leaking into host applications in three ways:
Bare
aelement selector —a { text-decoration: auto }inglobal.cssoverrode ALL<a>tags on the page, not just links inside the editor.Unscoped
.ProseMirrorselectors — All ~55.ProseMirrorCSS rules inprosemirror.cssaffected any ProseMirror instance on the page. If a host app used ProseMirror for another editor (comments, chat), SuperDoc's styles would strip list styling, force table collapse, override image sizing, etc..sr-onlyutility class collision — The DomPainter injected a.sr-onlyclass at runtime that collides with Bootstrap, Tailwind, and many other frameworks that use the same class name.Solution
This follows the CKEditor 5 model — the industry standard for editor CSS isolation. CKEditor scopes all content styles under
.ck-content, we scope ours under.super-editor.SuperDoc already did this for ~90% of its CSS. This PR closes the remaining gaps:
aselector fromglobal.css— the scoped.super-editor arule already handles editor links.ProseMirrorand.ProseMirror-*selectors under.super-editorinprosemirror.css(~55 selectors).sd-*selectors in extension CSS files:structured-content.css,document-section.css,noderesizer.css,ai.css,page-number.cssaiTextAppear→superdoc-aiTextAppear,ai-pulse→superdoc-ai-pulse.sr-only→.superdoc-sr-onlyin DomPainter runtime stylesWhy this approach
import 'lib/style.css'@layerParent class scoping is the simplest, most compatible approach. It's what CKEditor 5 and Quill use in production.
What is NOT changed
superdoc/package CSS — already scoped to.superdoc,.super-editor,.superdoc-toolbarisolation.css— inward protection (all: revert), already correctly scoped.superdoc-*(except.sr-onlywhich we fix)document.body, kept global with unique[data-theme]selectors.presentation-editor__*selectors — already namespaced, render in DomPainter layerTest plan
pnpm --filter super-editor test— 672 files, 6345 tests passpnpm --filter @superdoc/painter-dom test— 23 files, 629 tests pass (.superdoc-sr-onlyassertions updated)css-no-bleed.test.jslint test — structurally verifies no bare element selectors, all.ProseMirrorscoped under.super-editor, no generic utility class collisions (catches future regressions)