Bidirectional CSS-HTML converter with smart selector generation
Convert between CSS and inline styles effortlessly. Perfect for email templates, production optimization, and development workflows.
- β Full CSS selector support - element, class, descendant, compound selectors
- β
Preserve responsive styles -
@media,@import,@keyframes,@font-face - β
Keep pseudo-classes -
:hover,:focus,:activein<style>tags - β Smart style merging - respects CSS specificity and existing inline styles
- β Smart selector generation - uses existing classes, creates nested selectors
- β Style deduplication - groups identical styles under combined selectors
- β Minimal HTML changes - prioritizes existing structure over auto-classes
- β
Two output modes - internal
<style>tag or external CSS file
npm install css-inliner
# or
pnpm add css-inliner
# or
yarn add css-inlinerimport { inlineCSS } from 'css-inliner';
const html = `
<style>
.header { color: blue; font-size: 24px; }
.header:hover { color: darkblue; }
</style>
<div class="header">Hello World</div>
`;
const result = await inlineCSS(html);
console.log(result);
// Output:
// <style>.header:hover { color: darkblue; }</style>
// <div class="header" style="color:blue;font-size:24px">Hello World</div>import { reverseCSSInternal } from 'css-inliner';
const html = `
<div class="header" style="color:blue;font-size:24px">Hello</div>
<div class="header" style="color:blue;font-size:24px">World</div>
`;
const result = await reverseCSSInternal(html);
console.log(result);
// Output:
// <style>
// .header { color:blue; font-size:24px; }
// </style>
// <div class="header">Hello</div>
// <div class="header">World</div>import { reverseCSSExternal } from 'css-inliner';
import { writeFileSync } from 'fs';
const html = `
<div class="nav" style="display:flex;gap:20px">Navigation</div>
`;
const { html: htmlOutput, css: cssOutput } = await reverseCSSExternal(html);
writeFileSync('index.html', htmlOutput);
writeFileSync('styles.css', cssOutput);
// index.html:
// <link rel="stylesheet" href="styles.css">
// <div class="nav">Navigation</div>
// styles.css:
// .nav { display:flex; gap:20px; }Converts CSS rules to inline styles.
Parameters:
input- HTML string or URL
Returns: HTML string with inlined CSS
Features:
- Inlines all CSS rules into element
styleattributes - Preserves
@media,@import,@keyframes,@font-facein<style>tag - Keeps pseudo-classes (
:hover,:focus) in<style>tag - Respects CSS specificity
- Merges with existing inline styles
Extracts inline styles to internal CSS (<style> tag).
Parameters:
input- HTML string or URL
Returns: HTML string with CSS in <style> tag
Smart Selector Strategy:
- Use existing classes -
.header,.nav-list - Create nested selectors -
.parent .child,.nav a - Use element selectors -
body,html,h1 - Auto-generate only when needed -
.auto-style-1,.auto-style-2
Features:
- Deduplicates identical styles
- Preserves existing CSS (
@media,:hover, etc.) - Minimal HTML modifications
- Clean, semantic CSS output
Extracts inline styles to external CSS file.
Parameters:
input- HTML string or URL
Returns: Object with html and css strings
Use Case: Production websites that benefit from browser caching
const { html, css } = await reverseCSSExternal(inlinedHTML);
// html: contains <link rel="stylesheet" href="styles.css">
// css: all extracted CSS rules// Convert CSS to inline for email clients
const emailHTML = await inlineCSS(template);
sendEmail(emailHTML);// Extract inline CSS to cacheable file
const { html, css } = await reverseCSSExternal(buildOutput);
writeFileSync('index.html', html);
writeFileSync('styles.css', css);// Convert messy inline styles to readable CSS
const readable = await reverseCSSInternal(legacyHTML);// Chain conversions for different outputs
const inlined = await inlineCSS(original);
const withStyleTag = await reverseCSSInternal(inlined);
const { html, css } = await reverseCSSExternal(withStyleTag);const html = `
<style>
.container { max-width: 1200px; }
@media (max-width: 768px) {
.container { max-width: 100%; }
}
</style>
<div class="container">Content</div>
`;
const result = await inlineCSS(html);
// @media queries are preserved in <style> tag
// Regular styles are inlinedconst html = `
<style>
@import url('https://fonts.googleapis.com/css2?family=Roboto');
body { font-family: 'Roboto', sans-serif; }
</style>
<body>Text</body>
`;
const result = await inlineCSS(html);
// @import is preserved
// font-family is inlined with proper quote escapingconst html = `
<div class="nav">
<a style="color:blue;text-decoration:none">Link 1</a>
<a style="color:blue;text-decoration:none">Link 2</a>
</div>
`;
const result = await reverseCSSInternal(html);
// Output:
// <style>
// .nav a { color:blue; text-decoration:none; }
// </style>
// <div class="nav">
// <a>Link 1</a>
// <a>Link 2</a>
// </div>- Fast parsing - Uses optimized CSS and HTML parsers
- Minimal overhead - Efficient DOM traversal and style matching
- Memory efficient - Streaming-friendly architecture
# Run tests
pnpm test
# Watch mode
pnpm test:watch
# Coverage
pnpm test:coverageContributions are welcome! Please feel free to submit a Pull Request.
MIT Β© Osman Beyhan
Built with:
- css-tree - CSS parser
- htmlparser2 - HTML parser
- dom-serializer - DOM to HTML
Made with β€οΈ for developers who work with HTML and CSS