Skip to main content

High-performance library for inlining CSS into HTML 'style' attributes

Project description

css_inline

build status pypi versions license codecov.io gitter

css_inline is a high-performance library for inlining CSS into HTML 'style' attributes.

This library is designed for scenarios such as preparing HTML emails or embedding HTML into third-party web pages.

For instance, the library transforms HTML like this:

<html>
  <head>
    <style>h1 { color:blue; }</style>
  </head>
  <body>
    <h1>Big Text</h1>
  </body>
</html>

into:

<html>
  <head></head>
  <body>
    <h1 style="color:blue;">Big Text</h1>
  </body>
</html>
  • Uses reliable components from Mozilla's Servo project
  • 10-500x faster than alternatives
  • Inlines CSS from style and link tags
  • Removes style and link tags
  • Resolves external stylesheets (including local files)
  • Optionally caches external stylesheets
  • Can process multiple documents in parallel
  • Works on Linux, Windows, macOS and in the browser via PyOdide
  • Supports HTML5 & CSS3
  • Tested on CPython 3.10, 3.11, 3.12, 3.13, 3.14 and PyPy 3.11.

Playground

If you'd like to try css-inline, you can check the WebAssembly-powered playground to see the results instantly.

Installation

Install with pip:

pip install css_inline

Pre-compiled wheels are available for most popular platforms. If not available for your platform, a Rust compiler will be needed to build this package from source. Rust version 1.65 or higher is required.

Usage

import css_inline

HTML = """<html>
<head>
    <style>h1 { color:blue; }</style>
</head>
<body>
    <h1>Big Text</h1>
</body>
</html>"""

inlined = css_inline.inline(HTML)
# HTML becomes this:
#
# <html>
# <head>
#    <style>h1 { color:blue; }</style>
# </head>
# <body>
#     <h1 style="color:blue;">Big Text</h1>
# </body>
# </html>

Note that css-inline automatically adds missing html and body tags, so the output is a valid HTML document.

Alternatively, you can inline CSS into an HTML fragment. Structural tags (<html>, <head>, <body>) are stripped from the output; only their contents are preserved. Use inline if you need to keep the full document structure:

FRAGMENT = """<main>
<h1>Hello</h1>
<section>
<p>who am i</p>
</section>
</main>"""

CSS = """
p {
    color: red;
}

h1 {
    color: blue;
}
"""

inlined = css_inline.inline_fragment(FRAGMENT, CSS)
# HTML becomes this:
# <main>
# <h1 style="color: blue;">Hello</h1>
# <section>
# <p style="color: red;">who am i</p>
# </section>
# </main>

When there is a need to inline multiple HTML documents simultaneously, css_inline offers inline_many and inline_many_fragments functions. This feature allows for concurrent processing of several inputs, significantly improving performance when dealing with a large number of documents.

import css_inline

css_inline.inline_many(["<...>", "<...>"])

Under the hood, inline_many, spawns threads at the Rust layer to handle the parallel processing of inputs. This results in faster execution times compared to employing parallel processing techniques at the Python level.

Note: To fully benefit from inline_many, you should run your application on a multicore machine.

Configuration

For configuration options use the CSSInliner class:

import css_inline

inliner = css_inline.CSSInliner(keep_style_tags=True)
inliner.inline("...")
  • inline_style_tags. Specifies whether to inline CSS from "style" tags. Default: True
  • keep_style_tags. Specifies whether to keep "style" tags after inlining. Default: False
  • keep_link_tags. Specifies whether to keep "link" tags after inlining. Default: False
  • keep_at_rules. Specifies whether to keep "at-rules" (starting with @) after inlining. Default: False
  • minify_css. Specifies whether to remove trailing semicolons and spaces between properties and values. Default: False
  • base_url. The base URL used to resolve relative URLs. If you'd like to load stylesheets from your filesystem, use the file:// scheme. Default: None
  • load_remote_stylesheets. Specifies whether remote stylesheets should be loaded. Default: True
  • cache. Specifies caching options for external stylesheets (for example, StylesheetCache(size=5)). Default: None
  • extra_css. Extra CSS to be inlined. Default: None
  • preallocate_node_capacity. Advanced. Preallocates capacity for HTML nodes during parsing. This can improve performance when you have an estimate of the number of nodes in your HTML document. Default: 32
  • remove_inlined_selectors. Specifies whether to remove selectors that were successfully inlined from <style> blocks. Default: False
  • apply_width_attributes. Specifies whether to add width HTML attributes from CSS width properties on supported elements (table, td, th, img). Default: False
  • apply_height_attributes. Specifies whether to add height HTML attributes from CSS height properties on supported elements (table, td, th, img). Default: False

You can also skip CSS inlining for an HTML tag by adding the data-css-inline="ignore" attribute to it:

<head>
  <style>h1 { color:blue; }</style>
</head>
<body>
  <!-- The tag below won't receive additional styles -->
  <h1 data-css-inline="ignore">Big Text</h1>
</body>

The data-css-inline="ignore" attribute also allows you to skip link and style tags:

<head>
  <!-- Styles below are ignored -->
  <style data-css-inline="ignore">h1 { color:blue; }</style>
</head>
<body>
  <h1>Big Text</h1>
</body>

Alternatively, you may keep style from being removed by using the data-css-inline="keep" attribute. This is useful if you want to keep @media queries for responsive emails in separate style tags. Such tags will be kept in the resulting HTML even if the keep_style_tags option is set to false.

<head>
  <!-- Styles below are not removed -->
  <style data-css-inline="keep">h1 { color:blue; }</style>
</head>
<body>
  <h1>Big Text</h1>
</body>

Another possibility is to set keep_at_rules option to true. At-rules cannot be inlined into HTML therefore they get removed by default. This is useful if you want to keep at-rules, e.g. @media queries for responsive emails in separate style tags but inline any styles which can be inlined. Such tags will be kept in the resulting HTML even if the keep_style_tags option is explicitly set to false.

<head>
  <!-- With keep_at_rules=true "color:blue" will get inlined into <h1> but @media will be kept in <style> -->
  <style>h1 { color: blue; } @media (max-width: 600px) { h1 { font-size: 18px; } }</style>
</head>
<body>
  <h1>Big Text</h1>
</body>

If you set the the minify_css option to true, the inlined styles will be minified by removing trailing semicolons and spaces between properties and values.

<head>
  <!-- With minify_css=True, the <h1> will have `style="color:blue;font-weight:bold"` -->
  <style>h1 { color: blue; font-weight: bold; }</style>
</head>
<body>
  <h1>Big Text</h1>
</body>

If you'd like to load stylesheets from your filesystem, use the file:// scheme:

import css_inline

# styles/email is relative to the current directory
inliner = css_inline.CSSInliner(base_url="file://styles/email/")
inliner.inline("...")

You can also cache external stylesheets to avoid excessive network requests:

import css_inline

inliner = css_inline.CSSInliner(
    cache=css_inline.StylesheetCache(size=5)
)
inliner.inline("...")

Caching is disabled by default.

XHTML compatibility

If you'd like to work around some XHTML compatibility issues like closing empty tags (<hr> vs. <hr/>), you can use the following snippet that involves lxml:

import css_inline
from lxml import html, etree

document = "..."  # Your HTML document
inlined = css_inline.inline(document)
tree = html.fromstring(inlined)
inlined = etree.tostring(tree).decode(encoding="utf-8")

Performance

css-inline is powered by efficient tooling from Mozilla's Servo project and significantly outperforms other Python alternatives in terms of speed. Most of the time it achieves over a 10x speed advantage compared to the next fastest alternative.

Here is the performance comparison:

Size css_inline 0.19.0 premailer 3.10.0 toronado 0.1.0 inlinestyler 0.2.5 pynliner 0.8.0
Basic 230 B 4.27 µs 85.05 µs (19.93x) 495.30 µs (116.05x) 1.02 ms (238.87x) 867.79 µs (203.32x)
Realistic-1 8.58 KB 80.59 µs 1.03 ms (12.76x) 11.55 ms (143.29x) 26.37 ms (327.21x) 11.71 ms (145.36x)
Realistic-2 4.3 KB 46.88 µs 1.44 ms (30.73x) ERROR 17.71 ms (377.77x) ERROR
GitHub page 1.81 MB 17.57 ms 10.78 s (613.48x) ERROR ERROR ERROR

The "Basic" case was obtained by benchmarking the example from the Usage section. Note that the toronado, inlinestyler, and pynliner libraries encountered errors when used to inline CSS in the last scenarios.

The benchmarking code is available in the benches/bench.py file. The benchmarks were conducted using the stable rustc 1.91, Python 3.14.2 on Ryzen 9 9950X.

Comparison with other libraries

Besides performance, css-inline differs from other Python libraries for CSS inlining.

  • Generally supports more CSS features than other libraries (for example, toronado and pynliner do not support pseudo-elements);
  • It has fewer configuration options and is not as flexible as premailer;
  • Works on fewer platforms than LXML-based libraries (premailer, inlinestyler, toronado, and optionally pynliner);
  • Does not have debug logs yet;
  • Supports only HTML 5.

Further reading

If you want to know how this library was created & how it works internally, you could take a look at these articles:

License

This project is licensed under the terms of the MIT license.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

css_inline-0.21.0.tar.gz (73.5 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

css_inline-0.21.0-pp311-pypy311_pp73-manylinux_2_24_x86_64.whl (2.0 MB view details)

Uploaded PyPymanylinux: glibc 2.24+ x86-64

css_inline-0.21.0-pp311-pypy311_pp73-manylinux_2_24_aarch64.whl (1.9 MB view details)

Uploaded PyPymanylinux: glibc 2.24+ ARM64

css_inline-0.21.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl (1.8 MB view details)

Uploaded PyPymacOS 10.12+ x86-64

css_inline-0.21.0-cp310-abi3-win_amd64.whl (1.9 MB view details)

Uploaded CPython 3.10+Windows x86-64

css_inline-0.21.0-cp310-abi3-win32.whl (1.6 MB view details)

Uploaded CPython 3.10+Windows x86

css_inline-0.21.0-cp310-abi3-pyemscripten_2025_0_wasm32.whl (527.5 kB view details)

Uploaded CPython 3.10+PyEmscripten 2025.0 wasm32

css_inline-0.21.0-cp310-abi3-musllinux_1_2_x86_64.whl (2.2 MB view details)

Uploaded CPython 3.10+musllinux: musl 1.2+ x86-64

css_inline-0.21.0-cp310-abi3-musllinux_1_2_armv7l.whl (2.0 MB view details)

Uploaded CPython 3.10+musllinux: musl 1.2+ ARMv7l

css_inline-0.21.0-cp310-abi3-musllinux_1_2_aarch64.whl (2.1 MB view details)

Uploaded CPython 3.10+musllinux: musl 1.2+ ARM64

css_inline-0.21.0-cp310-abi3-manylinux_2_24_armv7l.whl (1.8 MB view details)

Uploaded CPython 3.10+manylinux: glibc 2.24+ ARMv7l

css_inline-0.21.0-cp310-abi3-manylinux_2_24_aarch64.whl (1.9 MB view details)

Uploaded CPython 3.10+manylinux: glibc 2.24+ ARM64

css_inline-0.21.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (2.0 MB view details)

Uploaded CPython 3.10+manylinux: glibc 2.17+ x86-64

css_inline-0.21.0-cp310-abi3-manylinux_2_12_i686.manylinux2010_i686.whl (1.9 MB view details)

Uploaded CPython 3.10+manylinux: glibc 2.12+ i686

css_inline-0.21.0-cp310-abi3-macosx_10_12_x86_64.whl (1.8 MB view details)

Uploaded CPython 3.10+macOS 10.12+ x86-64

css_inline-0.21.0-cp310-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl (3.6 MB view details)

Uploaded CPython 3.10+macOS 10.12+ universal2 (ARM64, x86-64)macOS 10.12+ x86-64macOS 11.0+ ARM64

File details

Details for the file css_inline-0.21.0.tar.gz.

File metadata

  • Download URL: css_inline-0.21.0.tar.gz
  • Upload date:
  • Size: 73.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.12

File hashes

Hashes for css_inline-0.21.0.tar.gz
Algorithm Hash digest
SHA256 ffac99be05a7db91199ebf55360e0950eabb7b18a3c35c1124ad053a18224403
MD5 72f3b44f84f8bb82f0181777f4339283
BLAKE2b-256 41f9a29086bfe59f88fe0a70d5b53efb8dd161e6decc360d066f54af6e3663e0

See more details on using hashes here.

File details

Details for the file css_inline-0.21.0-pp311-pypy311_pp73-manylinux_2_24_x86_64.whl.

File metadata

File hashes

Hashes for css_inline-0.21.0-pp311-pypy311_pp73-manylinux_2_24_x86_64.whl
Algorithm Hash digest
SHA256 3c3997f0e685ddaf17378026e23f3aeabfc493e8e1b26dd06d6f7e74ac44922a
MD5 be983f28ca707cd57c6dfe4dcc9a82f4
BLAKE2b-256 17e44f807fe89e3d60de0aeed610c67ac6959a3341ca0e111c273dc887209f0e

See more details on using hashes here.

File details

Details for the file css_inline-0.21.0-pp311-pypy311_pp73-manylinux_2_24_aarch64.whl.

File metadata

File hashes

Hashes for css_inline-0.21.0-pp311-pypy311_pp73-manylinux_2_24_aarch64.whl
Algorithm Hash digest
SHA256 3deffb1578db2a69b9d39c4b416f29b7517f9ebf27468fe857ff7f30f3acd8c4
MD5 8466cd1d6a9640df73c6e845568ffa39
BLAKE2b-256 0accbc11835769de99ea291e7f70dfdf8d361e8f27e67c98b984d9a6cf78fad1

See more details on using hashes here.

File details

Details for the file css_inline-0.21.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for css_inline-0.21.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 a5893a202a87619687a7cbae1112aba4ef9d2e0abde523b73c5217b30268c124
MD5 f934d741fbfd93c4f455ef3217f5302f
BLAKE2b-256 98b2fe998426f99198683c94c9824d523939cd478cee90beb23ba60129c6c0cd

See more details on using hashes here.

File details

Details for the file css_inline-0.21.0-cp310-abi3-win_amd64.whl.

File metadata

  • Download URL: css_inline-0.21.0-cp310-abi3-win_amd64.whl
  • Upload date:
  • Size: 1.9 MB
  • Tags: CPython 3.10+, Windows x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.12

File hashes

Hashes for css_inline-0.21.0-cp310-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 1fc6f1351f29d11d0e10f27752a921c545d808d952c005df7884ff7d512f36d2
MD5 4ff4c38bfa46e10c4ccc4adc24f70e1f
BLAKE2b-256 c068946dff8af26c93dc04c80be5470b5d8294e66df96c9b307f9b45c88a1c85

See more details on using hashes here.

File details

Details for the file css_inline-0.21.0-cp310-abi3-win32.whl.

File metadata

  • Download URL: css_inline-0.21.0-cp310-abi3-win32.whl
  • Upload date:
  • Size: 1.6 MB
  • Tags: CPython 3.10+, Windows x86
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.12

File hashes

Hashes for css_inline-0.21.0-cp310-abi3-win32.whl
Algorithm Hash digest
SHA256 d1cefe5abaeaefd5296fd9e299b845ed1ac211456f44a0497721473c8f1cb2b1
MD5 4f267446c9b699ce6656e356d67b9b25
BLAKE2b-256 f6add4931a7429067a065cf706d0ec98d6688dfd43bde269fc2db675bc32341e

See more details on using hashes here.

File details

Details for the file css_inline-0.21.0-cp310-abi3-pyemscripten_2025_0_wasm32.whl.

File metadata

File hashes

Hashes for css_inline-0.21.0-cp310-abi3-pyemscripten_2025_0_wasm32.whl
Algorithm Hash digest
SHA256 23c8983fce21b411ff22e1185f202b8b87d254dcd5b6de4cb2afa9affbdf2e79
MD5 fbc08e78889ea3301b186ccbb9a4e601
BLAKE2b-256 90253ef834fc48cc21f53442346fbe45194ebf151cee2cc2cfa49f8cf1f797e2

See more details on using hashes here.

File details

Details for the file css_inline-0.21.0-cp310-abi3-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for css_inline-0.21.0-cp310-abi3-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 06d32348b0a8771fad28893d54b568402e81d66601098c3e5b355acbb11c4741
MD5 45d8482dd76c390fcf4e75fdc0eb93aa
BLAKE2b-256 2f0d84e004144c180d95bfeb60365fb0101e7dc4baed2af363dfe7ef3c7352d7

See more details on using hashes here.

File details

Details for the file css_inline-0.21.0-cp310-abi3-musllinux_1_2_armv7l.whl.

File metadata

File hashes

Hashes for css_inline-0.21.0-cp310-abi3-musllinux_1_2_armv7l.whl
Algorithm Hash digest
SHA256 d04317dd0b81ad9e713f9593c509a8924b601a75391314f66d7d0b1433492c3e
MD5 bec2a76ab67095acb721b74a23d0916c
BLAKE2b-256 946e0b6b77ac4397fcde2c1a1b1fb6816a0e9e895119031b6a98cd7bcc539eb9

See more details on using hashes here.

File details

Details for the file css_inline-0.21.0-cp310-abi3-musllinux_1_2_aarch64.whl.

File metadata

File hashes

Hashes for css_inline-0.21.0-cp310-abi3-musllinux_1_2_aarch64.whl
Algorithm Hash digest
SHA256 140eb60562cc1ebe830b3ab1c82a761ff8e2a421c3c665e8afa501d66e673ddf
MD5 534a7a4e1577177a5d375fec5da86ae2
BLAKE2b-256 229fe4dc1bc7a45e25e9a83104fbd1d72580f4a54ab1370a6ba8618f01480e54

See more details on using hashes here.

File details

Details for the file css_inline-0.21.0-cp310-abi3-manylinux_2_24_armv7l.whl.

File metadata

File hashes

Hashes for css_inline-0.21.0-cp310-abi3-manylinux_2_24_armv7l.whl
Algorithm Hash digest
SHA256 46c79cf3df221375925ad1f47131613a79ce9df7d673525bf02316dc9522f5a0
MD5 cc859da8748c979006d189372489e817
BLAKE2b-256 5929a5228664570d6d149fe8cf3fb54db16cd6f8a2b08637d7574333ff4aa25a

See more details on using hashes here.

File details

Details for the file css_inline-0.21.0-cp310-abi3-manylinux_2_24_aarch64.whl.

File metadata

File hashes

Hashes for css_inline-0.21.0-cp310-abi3-manylinux_2_24_aarch64.whl
Algorithm Hash digest
SHA256 d2b3fd682c422f17efce087012703e2e0d1f656218e399e2483c31df5a94c050
MD5 c9c1de35708fd4799a70659a916a84e4
BLAKE2b-256 97b342e8469986ae3c329bc2cd61dbf892bee35e89b8afa9bcf60adc6d247298

See more details on using hashes here.

File details

Details for the file css_inline-0.21.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for css_inline-0.21.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 f51ed1ae57c065175dd4d0888a633cf416dcc53b6b53d8c342dba2bade768447
MD5 7f2376713458c58b992eb79b309bb0df
BLAKE2b-256 d1b692abb1f390eb96c3423fe04c21b53e8b42a56813babdd811a2adf7d46439

See more details on using hashes here.

File details

Details for the file css_inline-0.21.0-cp310-abi3-manylinux_2_12_i686.manylinux2010_i686.whl.

File metadata

File hashes

Hashes for css_inline-0.21.0-cp310-abi3-manylinux_2_12_i686.manylinux2010_i686.whl
Algorithm Hash digest
SHA256 118d65e5e0fe83fcdfaf1a8d19ec142c9fc82e0db3c91c7b90125f0f799207c4
MD5 c423d16de8417916b9d06e4b5158abdf
BLAKE2b-256 fc4088365580e2706c8c6d3c6d4d73656ad84155ce17bd3f8a4bd8216309977c

See more details on using hashes here.

File details

Details for the file css_inline-0.21.0-cp310-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for css_inline-0.21.0-cp310-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 109520593d20fbf6ae7f82bcf348db6a5095a24dddff8ccc1185cbbc03505c45
MD5 5f7dce76e99ba8be6decb80e2a788baf
BLAKE2b-256 4c5f769703f96620ef9a783ba1d855659813b5e31c19d1f06e992560a743c7c2

See more details on using hashes here.

File details

Details for the file css_inline-0.21.0-cp310-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl.

File metadata

File hashes

Hashes for css_inline-0.21.0-cp310-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl
Algorithm Hash digest
SHA256 d16e8ecf04f7845de5b001c99b0e139353e0887ddf71a881d83fad66a5fdfed2
MD5 8f39617322f2e632eef68141f8e4405a
BLAKE2b-256 f55012aa4008dfaf3d327c7834ad87dba616c9362127b015acf583b1f0849754

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page