Converts CSS styles into inline style attributes in your HTML code.
n. e•mog•ri•fi•er [\ē-'mä-grƏ-,fī-Ər] - a utility for changing completely the
nature or appearance of HTML email, esp. in a particularly fantastic or bizarre
manner
Emogrifier converts CSS styles into inline style attributes in your HTML code.
This ensures proper display on email and mobile device readers that lack
stylesheet support.
This utility was developed as part of Intervals
to deal with the problems posed by certain email clients (namely Outlook 2007
and GoogleMail) when it comes to the way they handle styling contained in HTML
emails. As many web developers and designers already know, certain email
clients are notorious for their lack of CSS support. While attempts are being
made to develop common email standards,
implementation is still a ways off.
The primary problem with uncooperative email clients is that most tend to only
regard inline CSS, discarding all <style>
elements and links to stylesheets
in <link>
elements. Emogrifier solves this problem by converting CSS styles
into inline style attributes in your HTML code.
Emogrifier automagically transmogrifies your HTML by parsing your CSS and
inserting your CSS definitions into tags within your HTML based on your CSS
selectors.
For installing emogrifier, either add pelago/emogrifier
to the require
section in your project’s composer.json
, or you can use composer as below:
composer require pelago/emogrifier
See https://getcomposer.org/ for more information and documentation.
The most basic way to use the CssInliner
class is to create an instance with
the original HTML, inline the external CSS, and then get back the resulting
HTML:
use Pelago\Emogrifier\CssInliner;
…
$visualHtml = CssInliner::fromHtml($html)->inlineCss($css)->render();
If there is no external CSS file and all CSS is located within <style>
elements in the HTML, you can omit the $css
parameter:
$visualHtml = CssInliner::fromHtml($html)->inlineCss()->render();
If you would like to get back only the content of the <body>
element instead
of the complete HTML document, you can use the renderBodyContent
method
instead:
$bodyContent = $visualHtml = CssInliner::fromHtml($html)->inlineCss()
->renderBodyContent();
If you would like to modify the inlining process with any of the available
options, you will need to call the corresponding methods
before inlining the CSS. The code then would look like this:
$visualHtml = CssInliner::fromHtml($html)->disableStyleBlocksParsing()
->inlineCss($css)->render();
There are also some other HTML-processing classes available
(all of which are subclasses of AbstractHtmlProcessor
) which you can use
to further change the HTML after inlining the CSS.
(For more details on the classes, please have a look at the sections below.)
CssInliner
and all HTML-processing classes can share the same DOMDocument
instance to work on:
use Pelago\Emogrifier\CssInliner;
use Pelago\Emogrifier\HtmlProcessor\CssToAttributeConverter;
use Pelago\Emogrifier\HtmlProcessor\HtmlPruner;
…
$cssInliner = CssInliner::fromHtml($html)->inlineCss($css);
$domDocument = $cssInliner->getDomDocument();
HtmlPruner::fromDomDocument($domDocument)->removeElementsWithDisplayNone()
->removeRedundantClassesAfterCssInlined($cssInliner);
$finalHtml = CssToAttributeConverter::fromDomDocument($domDocument)
->convertCssToVisualAttributes()->render();
The HtmlNormalizer
class normalizes the given HTML in the following ways:
The class can be used like this:
use Pelago\Emogrifier\HtmlProcessor\HtmlNormalizer;
…
$cleanHtml = HtmlNormalizer::fromHtml($rawHtml)->render();
The CssToAttributeConverter
converts a few style attributes values to visual
HTML attributes. This allows to get at least a bit of visual styling for email
clients that do not support CSS well. For example, style="width: 100px"
will be converted to width="100"
.
The class can be used like this:
use Pelago\Emogrifier\HtmlProcessor\CssToAttributeConverter;
…
$visualHtml = CssToAttributeConverter::fromHtml($rawHtml)
->convertCssToVisualAttributes()->render();
You can also have the CssToAttributeConverter
work on a DOMDocument
:
$visualHtml = CssToAttributeConverter::fromDomDocument($domDocument)
->convertCssToVisualAttributes()->render();
The CssVariableEvaluator
class can be used to apply the values of CSS
variables defined in inline style attributes to inline style properties which
use them.
For example, the following CSS defines and uses a custom property:
:root {
--text-color: green;
}
p {
color: var(--text-color);
}
After CssInliner
has inlined that CSS on the (contrived) HTML
<html><body><p></p></body></html>
, it will look like this:
<html style="--text-color: green;">
<body>
<p style="color: var(--text-color);">
<p>
</body>
</html>
The CssVariableEvaluator
method evaluateVariables
will apply the value of
--text-color
so that the paragraph style
attribute becomes color: green;
.
It can be used like this:
use Pelago\Emogrifier\HtmlProcessor\CssVariableEvaluator;
…
$evaluatedHtml = CssVariableEvaluator::fromHtml($html)
->evaluateVariables()->render();
You can also have the CssVariableEvaluator
work on a DOMDocument
:
$evaluatedHtml = CssVariableEvaluator::fromDomDocument($domDocument)
->evaluateVariables()->render();
The HtmlPruner
class can reduce the size of the HTML by removing elements with
a display: none
style declaration, and/or removing classes from class
attributes that are not required.
It can be used like this:
use Pelago\Emogrifier\HtmlProcessor\HtmlPruner;
…
$prunedHtml = HtmlPruner::fromHtml($html)->removeElementsWithDisplayNone()
->removeRedundantClasses($classesToKeep)->render();
The removeRedundantClasses
method accepts an allowlist of names of classes
that should be retained. If this is a post-processing step after inlining CSS,
you can alternatively use removeRedundantClassesAfterCssInlined
, passing it
the CssInliner
instance that has inlined the CSS (and having the HtmlPruner
work on the DOMDocument
). This will use information from the CssInliner
to
determine which classes are still required (namely, those used in uninlinable
rules that have been copied to a <style>
element):
$prunedHtml = HtmlPruner::fromDomDocument($cssInliner->getDomDocument())
->removeElementsWithDisplayNone()
->removeRedundantClassesAfterCssInlined($cssInliner)->render();
The removeElementsWithDisplayNone
method will not remove any elements which
have the class -emogrifier-keep
. So if, for example, there are elements which
by default have display: none
but are revealed by an @media
rule, or which
are intended as a preheader, you can add that class to those elements. The
paragraph in this HTML snippet will not be removed even though it has
display: none
(which has presumably been applied by CssInliner::inlineCss()
from a CSS rule .preheader { display: none; }
):
<p class="preheader -emogrifier-keep" style="display: none;">
Hello World!
</p>
The removeRedundantClassesAfterCssInlined
(or removeRedundantClasses
)
method, if invoked after removeElementsWithDisplayNone
, will remove the
-emogrifier-keep
class.
There are several options that you can set on the CssInliner
instance before
calling the inlineCss
method:
->disableStyleBlocksParsing()
- By default, CssInliner
will grab<style>
blocks in the HTML and will apply the CSS styles as inline<style>
blocks will then be removedCssInliner
<style>
blocks in the HTML and does not parse them, you should<style>
blocksCssInliner
to->disableInlineStyleAttributesParsing()
- By default, CssInliner
->addAllowedMediaType(string $mediaName)
- By default, CssInliner
all
, screen
and print
. If you want to keep->removeAllowedMediaType(string $mediaName)
- You can use this->addExcludedSelector(string $selector)
- Keeps elements from$cssInliner->addExcludedSelector('.message-preview');
$cssInliner->addExcludedSelector('.message-preview *');
->addExcludedCssSelector(string $selector)
- Contrary toaddExcludedSelector
, which excludes HTML nodes, this method excludes CSS* { margin: 0; padding: 0; font-size: 100% }
)..example
will not exclude p .example
.$cssInliner->addExcludedCssSelector('*');
$cssInliner->addExcludedCssSelector('form');
->removeExcludedCssSelector(string $selector)
- Removes previously added$cssInliner->removeExcludedCssSelector('form');
Emogrifier
class to the CssInliner
classOld code using Emogrifier
:
$emogrifier = new Emogrifier($html);
$html = $emogrifier->emogrify();
New code using CssInliner
:
$html = CssInliner::fromHtml($html)->inlineCss()->render();
NB: In this example, the old code removes elements with display: none;
while the new code does not, as the default behaviors of the old and
the new class differ in this regard.
Old code using Emogrifier
:
$emogrifier = new Emogrifier($html, $css);
$emogrifier->enableCssToHtmlMapping();
$html = $emogrifier->emogrify();
New code using CssInliner
and family:
$domDocument = CssInliner::fromHtml($html)->inlineCss($css)->getDomDocument();
HtmlPruner::fromDomDocument($domDocument)->removeElementsWithDisplayNone();
$html = CssToAttributeConverter::fromDomDocument($domDocument)
->convertCssToVisualAttributes()->render();
Emogrifier currently supports the following
CSS selectors:
~
(one word within a whitespace-separated list of words)|
(either exact value match or prefix followed by a hyphen)^
(prefix match)$
(suffix match)*
(substring match)p:first-of-type
but not *:first-of-type
)The following selectors are not implemented yet:
<style>
element in the HTML – including (but notRules involving the following selectors cannot be applied as inline styles.
They will, however, be preserved and copied to a <style>
element in the HTML:
:hover
)::after
)@media
rules. Media queries can be very!important
font-size
@media
rule would not override the font size for p
<p style="font-size: 16px;">
in the HTML, without the !important
directive!important
would not be necessary if the CSS were not inlined):p {
font-size: 16px;
}
@media (max-width: 640px) {
p {
font-size: 14px !important;
}
}
Any CSS custom properties (variables) defined in @media
rules cannot be@media
rules using custom properties (with var()
) would still be able to@media
rules) in email::after
) or dynamic pseudo-classes (such as :hover
) – it is<style>
@media
rules, with the same caveats applying.<style>
blocks from your HTML, but it will not grab CSS files<link>
elements or @import
rules (though it will leave themPlease have a look at our
API and deprecation policy.
Contributions in the form of bug reports, feature requests, or pull requests are
more than welcome. 🙏 Please have a look at our
contribution guidelines to learn more about how to
contribute to Emogrifier.
branch-alias
entry to