css-inline

High-performance library for inlining CSS into HTML style attributes

Library
Cargo
v0.21.2
318 stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum. How we score it →
83 /100 Excellent
Development Activity 96
Maintenance 100
Community 56
Maturity 60
Momentum 20

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation. How we score it →
84 /100 Excellent
Architecture 87
Code Quality 88
Innovation 82
Learning Curve 80

css-inline takes an HTML document and its associated CSS (from <style> tags, linked stylesheets, or passed explicitly) and rewrites it so every matched rule is applied directly as an inline style attribute on the corresponding element. This is the standard fix for HTML email, since most email clients strip or ignore <style> blocks and only honor inline styles — css-inline handles selector matching, specificity, cascade order, and CSS minification to produce email-safe HTML.

The core is written in Rust on top of html5ever and cssparser/selectors (the same parsing crates used by Servo), making it fast enough to inline thousands of emails per second, and the project ships first-class bindings for Python, Ruby, Node.js/WASM, PHP, Java, and C, plus a standalone CLI binary — so the same inlining engine can be dropped into almost any email-sending pipeline regardless of language.

What You Get

  • An Inliner builder API (inline_style_tags, keep_style_tags, keep_link_tags, base_url, load_remote_stylesheets, minify_css) for configuring inlining behavior
  • Correct CSS cascade and specificity resolution using the same selectors/cssparser engines that power Servo/Firefox
  • Optional remote stylesheet loading over HTTP via a reqwest-backed feature flag, with a local LRU stylesheet cache
  • A standalone css-inline CLI binary for inlining files or piped HTML from the shell
  • Official bindings for Python, Ruby, Node.js (WASM), PHP, Java, and C, sharing the same Rust core for consistent behavior across languages

Common Use Cases

  • Preparing transactional or marketing HTML emails so styles render correctly in Outlook, Gmail, and other clients that strip <style> tags
  • Batch-inlining thousands of templated emails at send time without a Node.js/JS build-step dependency
  • Converting a normal CSS-linked HTML template into a self-contained inlined document for embedding or archiving
  • Using the CLI in a CI/build pipeline to pre-process email templates before they’re committed or deployed
  • Calling the shared Rust core from Python/Ruby/PHP email-sending code paths that need the same inlining semantics as the CLI

Under The Hood

Architecture — lib.rs implements the core pipeline: parse HTML into a DOM (via html5ever), collect and chunk CSS rules (CssChunk, compute_rule_chunk_indices), match each rule’s selectors against DOM nodes using the selectors crate, then rewrite or append style attribute content per node (rewrite_style_blocks, overwrite_style_node), with resolver.rs handling remote/local stylesheet fetching and parser.rs handling CSS-specific parsing concerns. Tech Stack — Pure Rust (edition 2021, MSRV 1.85) built on cssparser, html5ever, and selectors (the Servo/Firefox CSS engine components), with optional features gating reqwest (remote stylesheets), rayon (CLI parallelism), and lru (stylesheet caching) so consumers only pay for what they use; bindings/ contains separate crates/packages per target language (Python, Ruby, Node/WASM, PHP, Java, C) wrapping the same core. Code Quality — Dedicated test suites cover inlining correctness and CSS selector edge cases (test_inlining.rs, test_selectors.rs) plus CLI behavior (test_cli.rs), with codspeed-criterion-compat benchmarks tracking performance regressions in CI — a notable rigor level for a library whose value proposition is largely speed. API Design — The builder pattern (CSSInliner::options().keep_style_tags(true).inline(&html)) keeps common cases to one line while exposing every cascade/remote-loading knob explicitly; the same semantics are mirrored across all language bindings, so switching from the Python package to the Rust crate requires no behavioral relearning.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers