Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Css To Inline Styles Laravel Package

voku/css-to-inline-styles

voku/css-to-inline-styles converts CSS rules into inline style attributes on HTML elements, making it useful for email templates and clients that strip or ignore style tags. It is a PHP library for reliably inlining stylesheets directly into markup.

View on GitHub
Deep Wiki
Context7

Getting Started

Install via Composer:

composer require voku/css-to-inline-styles

The package wraps a single class, voku\CssToInlineStyles\CssToInlineStyles, so the first place to look is src/CssToInlineStyles.php. The two entry points you will use every day are the constructor (new CssToInlineStyles($html, $css)) and convert($html, $css), which both accept an HTML string and an optional CSS string and return HTML with <style>/linked rules merged onto each element's style attribute.

The minimal first use case is an email with an embedded <style> block:

use voku\CssToInlineStyles\CssToInlineStyles;

$converter = new CssToInlineStyles();
$html = view('emails.welcome', $data)->render();

// Reads <style> blocks and <link rel="stylesheet"> from the HTML automatically
$rendered = $converter->convert($html);

Mail::html($rendered, function ($message) use ($user) {
    $message->to($user->email)->subject('Welcome');
});

If you have a .css file on disk you want to use instead, pass it as the second argument to convert() — it takes priority over <style> blocks.

Implementation Patterns

Inlining inside a Mailable. The cleanest pattern is to bypass $this->view() and hand a fully rendered, inlined string to $this->html():

class OrderShipped extends Mailable
{
    public function build(CssToInlineStyles $converter)
    {
        $html = view('emails.order-shipped', $this->payload)->render();
        $css  = file_get_contents(resource_path('css/email.css'));

        return $this->html($converter->convert($html, $css));
    }
}

Binding as a singleton. The converter holds per-conversion state and setters (see Gotchas), so register it in a service provider and reset options per call — or create a fresh instance per conversion to avoid leaking flags:

$this->app->singleton(CssToInlineStyles::class, fn () => new CssToInlineStyles());

Blade + CSS partials. Treat your email as two artifacts: a Blade view for markup and a plain email.css for styles. Keep the CSS file limited to classes you actually use in the email — CSS selectors that don't match still cost DOM traversal time. The Blade view must be fully rendered before conversion, so pass data explicitly and avoid @extends/@section if the layout file lives outside resources/views/emails.

Preprocessing pipelines. A common workflow is Blade render → optional HTML minification → CssToInlineStyles → send. Do not run the converter before Blade rendering — Blade directives are not understood by the CSS-to-XPath layer and will be treated as literal text.

Queue jobs. Conversion is CPU-bound and DOM-heavy. Run it in a queued SendEmailJob and let the serialized Mailable hold the payload, not the inlined HTML. If you build the HTML in the job itself, keep the conversion inside the job's handle() so retries re-render rather than re-send a stale blob.

Multiple stylesheets. The library handles <link rel="stylesheet"> tags in the source HTML by resolving them locally (file path or URL) when convert() is called. If your CDN URL is behind a firewall or unreachable from the queue worker, pass the CSS string as the second argument instead — this is more deterministic and avoids network I/O at send time.

Testing. The convert() output is deterministic for fixed inputs, so snapshot-test it: $this->assertStringContainsString('style="color:#333"', $html). Assert on the specific properties you care about rather than the whole blob; the attribute order produced by DOMDocument is not stable across PHP versions.

Gotchas and Tips

Media queries are skipped. Email clients strip @media blocks anyway; if you need them, either rely on the built-in setExcludeMediaQueries() behavior or wrap the output and manually re-inject the @media block you want preserved after inlining.

Specificity is resolved, but source order is not guaranteed to match your mental model. The converter maps selectors to XPath, then applies whichever matching rule it finds last for a given property. If two rules of equal specificity set color, the winner depends on iteration order, not the order you wrote them in. Use !important sparingly — it is honored — but the safest rule is: avoid overlapping selectors in email CSS.

<style> and <link> behavior. By default the original <style> blocks are stripped from output (stripOriginalStyleTags). If you support a client that handles <style> (rare among webmail, but some desktop clients do), call $converter->setStripOriginalStyleTags(false) to keep them for graceful degradation. Conversely, setCleanup(true) removes the <style> blocks and any comments left over — good for delivery, bad for debugging.

Conditional comments break Outlook targeting. Outlook-specific markup like <!--[if mso]>...<![endif]--> is a comment node; if you rely on it, set setExcludeConditionalInlineStylesBlock(true) so the converter doesn't try to parse its contents. Otherwise you may find the conditional block's inner styles inlined into the wrong elements.

Blade escaping and special characters. setEncodeSpecialChars(true) forces entities to be encoded before DOM parsing. This matters when your HTML contains characters like &, non-breaking spaces, or curly quotes that DOMDocument would otherwise mangle. The default is usually safe, but preview in a real client — Litmus or even Gmail's web UI — before shipping.

DOMDocument is HTML4-ish. Malformed HTML — unclosed <p>, self-closing <br /> mixed with <br>, or SVG/contenteditable attributes — can be re-serialized in surprising ways (e.g., <p> siblings get re-nested). Run a quick tidy or HTML5 minify before conversion when using templates authored by non-engineers. Also budget for memory: DOMDocument::loadHTML on 200KB+ newsletters in a worker can push past memory_limit if you also hold the raw string.

Idempotency failure. Converting twice is not a no-op: the second pass sees the style="" attributes as inline styles and may overwrite them from <style> blocks still in the markup. Always start from a clean source — store the pre-inlined template or re-render — and never pipe the output back into convert().

Low-adoption risk. With 19 stars and zero dependent packages, upstream issue resolution is slow. Pin an exact version in composer.json, and consider symfony/css-selector as an explicit dependency (it is required transitively, but pinning makes the dependency visible in composer outdated). If you hit a parsing edge case the fork doesn't cover, the fallback is tijsverkoyen/css-to-inline-styles (the upstream) or the Premailer API — swapping is a one-file change since the public API is nearly identical.

No Laravel integration layer. This package is framework-agnostic — there are no service providers, facades, or config files. All wiring is your responsibility. Keep a small EmailInliner helper in app/Support/ so you can change out the underlying library later without touching every Mailable.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin