RidByte
Why You Should Always Prefix Tailwind in Embeddable Widgets
Learn why you should always prefix Tailwind CSS classes when building embeddable widgets. Real examples of CSS conflicts and how to fix them with one line of code.
Why You Should Always Prefix Tailwind in Embeddable Widgets
I'm building CommentBy, a commenting widget that embeds anywhere with a single <div> tag. I learned the hard way: if you use Tailwind in an embeddable widget, prefix your classes from day one.
Without prefixing, my widget's styles conflicted with every site that also used Tailwind. Buttons disappeared, layouts broke, some sites looked completely broken.
Here's the problem and the fix.
The Problem with Unprefixed Tailwind in Widgets
Your widget loads on someone else's site:
<!-- User adds this to their site -->
<div id="commentby_comment_box"></div>
<script defer src="https://cdn.commentby.com/widget.js"></script>
Your code renders HTML with Tailwind classes into their page. If they also use Tailwind, you have collision problems:
Scenario 1: Different theme configurations
- Their Tailwind defines blue-500 as #1E40AF
- Your Tailwind defines blue-500 as #3B82F6
- Result: Your blue buttons are the wrong shade of blue
Scenario 2: Different Tailwind versions
- They use Tailwind 2.x with different spacing scale
- You use Tailwind 3.x/4.x
- Result: Your p-4 has different padding than expected
Scenario 3: Custom color palettes
- They redefined gray-100 to be pink for their brand
- You use bg-gray-100 for comment cards
- Result: Pink comment cards
Scenario 4: Global style overrides (This was the worst)
/* Host site has this */
button {
background: transparent;
border: 2px solid black;
font-weight: bold;
}
Your widget's bg-blue-500 button suddenly has transparent background because their global styles override your utilities (unless you use !important).
Real Examples from CommentBy
Tech blog (Next.js + Tailwind):
- Used custom gray palette
- Result: My light gray backgrounds became bright pink
WordPress site:
- Theme had button { all: unset; } reset
- Result: My submit button became invisible
Ghost blog:
- Used !important on base text colors
- Result: All my text hierarchy broke
Marketing site:
- Had global * { box-sizing: content-box; }
- Result: All padding calculations broke, layout collapsed
The Solution: Prefix Everything
Use Tailwind's prefix feature from day one:
/* widget.css */
@import "tailwindcss" prefix(commentby) important;
Two directives:
1. prefix(commentby) - All classes get commentby: namespace
2. important - All utilities get !important to override host styles
Now your classes look like:
// Before (dangerous)
<div className="bg-white p-4 rounded-lg shadow-md">
<button className="bg-blue-500 px-6 py-2 text-white">
Submit
</button>
</div>
// After (safe)
<div className="commentby:bg-white commentby:p-4 commentby:rounded-lg commentby:shadow-md">
<button className="commentby:bg-blue-500 commentby:px-6 commentby:py-2 commentby:text-white">
Submit
</button>
</div>
Your .commentby\:bg-blue-500 won't conflict with their .bg-blue-500.
How Prefix Works
The prefix goes first, before all modifiers:
<div className="
commentby:p-4
commentby:md:p-6
commentby:lg:p-8
commentby:hover:bg-blue-500
commentby:focus:ring-2
commentby:dark:bg-gray-800
">
Not md:commentby:p-6 - the prefix is always first.
Generated CSS
Tailwind compiles to:
.commentby\:bg-blue-500 {
background-color: rgb(59 130 246) !important;
}
.commentby\:p-4 {
padding: 1rem !important;
}
@media (min-width: 768px) {
.commentby\:md\:p-6 {
padding: 1.5rem !important;
}
}
.commentby\:hover\:bg-blue-600:hover {
background-color: rgb(37 99 235) !important;
}
Every utility gets:
- Your prefix namespace (.commentby\:)
- !important flag
- Proper CSS escaping (\: for colons)
Why important is Critical
Without !important, host page styles still win:
/* Their site */
button {
background: transparent;
padding: 10px;
}
/* Your widget */
.commentby\:bg-blue-500 {
background-color: rgb(59 130 246); /* Lost - no !important */
}
Global button styles have higher specificity. Your button stays transparent.
With important, you win:
css
.commentby\:bg-blue-500 {
background-color: rgb(59 130 246) !important; /* Wins */
}
Implementation with Preact
Here's how I use it in CommentBy:
// Widget.jsx
import { render } from 'preact';
function CommentWidget() {
return (
<div className="commentby:bg-white commentby:p-6 commentby:rounded-lg commentby:shadow-lg">
<form className="commentby:space-y-4">
<textarea
className="commentby:w-full commentby:p-3 commentby:border commentby:rounded"
placeholder="Add a comment..."
/>
<button
className="commentby:bg-blue-500 commentby:text-white commentby:px-4 commentby:py-2 commentby:rounded commentby:hover:bg-blue-600"
>
Submit
</button>
</form>
</div>
);
}
// Mount to the page
const container = document.getElementById('commentby-widget');
render(<CommentWidget />, container);
CSS gets bundled into the same file with Vite, so it's one JavaScript file with inlined styles.
Shadow DOM Alternative
You could use Shadow DOM for complete isolation:
const container = document.getElementById('commentby-widget');
const shadowRoot = container.attachShadow({ mode: 'open' });
// Render Preact into Shadow DOM
render(<CommentWidget />, shadowRoot);
With Shadow DOM, you don't need prefix or important - styles are fully isolated.
Why I didn't use Shadow DOM:
- Can't inherit host site's font (looks disconnected)
- Can't inherit base text colors (feels like iframe)
- More complex event handling
- Accessibility concerns
- Want widget to feel integrated, not isolated
The prefix + important approach gives isolation where needed while allowing intentional inheritance.
Testing Strategy
Test your widget on sites with different CSS:
- Tailwind site (class conflicts)
- Bootstrap site (different utilities)
- WordPress with theme (global button styles)
- Ghost blog (custom CSS)
- Legacy site (old CSS, weird resets)
- Minimal site (baseline)
For each, verify:
- Layout integrity
- Colors match design
- Spacing correct
- Buttons render properly
- Responsive breakpoints work
Performance
Prefix adds minimal overhead:
- Without prefix: 45KB minified
- With prefix + important: 47KB minified
The 2KB increase (just the prefix string repeated) is worth it for zero conflicts across any site.
What I Wish I Knew
Start with prefix from day one. I had to refactor 150+ component files, updating every single className. It's tedious and error-prone.
Don't assume your test page represents real sites. Clean HTML with no CSS is nothing like a production WordPress site with 15 plugins and aggressive theme styles.
Test early on real sites with real CSS conflicts.
Conclusion
If you're building embeddable widgets with Tailwind:
- Use prefix from day one:
@import "tailwindcss" prefix(yourapp) important; - Prefix syntax:
yourapp:p-4,yourapp:md:p-6,yourapp:hover:bg-blue-500 - Always include
importantto override host styles - Test on real sites early (multiple CSS frameworks, versions, custom styles)
- Consider Shadow DOM only if you need total isolation
Don't learn this the hard way like I did. Prefix from the start.
Building CommentBy - a privacy-first commenting widget that embeds anywhere. Built with Preact and properly prefixed Tailwind. Try the demo.
What's your experience with CSS conflicts in embeddable widgets? Drop a comment.