twig/cssinliner-extra
Twig CssInliner Extension adds the inline_css filter to Twig templates, letting you inline CSS into HTML output. Useful for building email-friendly HTML with styles converted to inline attributes during rendering.
Install the Package
Add to composer.json:
"require": {
"twig/cssinliner-extra": "^3.24"
}
Run:
composer require twig/cssinliner-extra
Register the Twig Extension
In your AppServiceProvider or a dedicated Twig service provider:
use Twig\Extension\CssInlinerExtraExtension;
public function register()
{
$this->app->singleton(Twig\Extension\CssInlinerExtraExtension::class);
}
public function boot()
{
$this->app->make(Twig\Extension\CssInlinerExtraExtension::class)
->register($this->app->make('twig'));
}
First Use Case: Inline CSS in a Twig Template
{# resources/views/emails/welcome.twig #}
<div class="email-container">
<h1>Welcome!</h1>
<p>This is a test email.</p>
</div>
<style>
.email-container { font-family: Arial; padding: 20px; }
h1 { color: #333; }
</style>
{# Inline the CSS #}
{{ include('emails/welcome.twig') | inline_css }}
inline_css FilterCssInlinerExtraExtension for customization (e.g., remote flag for external CSS).Basic Inlining in Twig Templates
{{ content_with_styles | inline_css }}
<style> tags and external CSS (if remote: true is configured).Build-Time Processing with Artisan Create a custom Artisan command to pre-process emails:
// app/Console/Commands/InlineEmailCss.php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Twig\Environment;
use Twig\Extension\CssInlinerExtraExtension;
class InlineEmailCss extends Command
{
protected $signature = 'emails:inline-css
{--templates= : Path to template directory}
{--output= : Output directory for processed emails}';
public function handle(Environment $twig, CssInlinerExtraExtension $extension)
{
$templates = $this->option('templates');
$outputDir = $this->option('output');
// Load and process each template
foreach (glob($templates . '/*.twig') as $template) {
$content = file_get_contents($template);
$inlined = $twig->createTemplate($content)
->renderBlock('body', [], ['inline_css' => true]);
file_put_contents($outputDir . '/' . basename($template, '.twig') . '.html', $inlined);
}
}
}
Run:
php artisan emails:inline-css --templates=resources/views/emails --output=public/emails
Component-Driven Email Design Use Twig partials with embedded CSS:
{# resources/views/emails/partials/header.twig #}
<div class="header">
<img src="{{ logo_url }}" alt="Logo">
</div>
<style>
.header { background: #f5f5f5; padding: 20px; }
</style>
{# resources/views/emails/welcome.twig #}
{{ include('emails/partials/header.twig') | inline_css }}
<div class="content">
{{ include('emails/partials/content.twig') | inline_css }}
</div>
Laravel Mailables: Combine with Laravel’s Mailable classes for dynamic emails:
// app/Mail/WelcomeEmail.php
public function build()
{
return $this->markdown('emails.welcome')
->with([
'logo_url' => asset('images/logo.png'),
]);
}
Ensure the Twig template uses the inline_css filter.
Caching Inlined Emails: Cache processed emails to avoid runtime overhead:
$inlinedEmail = Cache::remember(
"email_{$user->id}_welcome_inlined",
now()->addHours(1),
fn() => $twig->createTemplate($content)->renderBlock('body', [], ['inline_css' => true])
);
Testing: Use Laravel’s MailFake to test inlined emails:
$this->mail->assertSent(WelcomeEmail::class, function ($mail) {
return str_contains($mail->html, 'style="font-family: Arial;"');
});
Silent CSS Dropping
@media, :hover, combinators like div > p).{# Fallback for unsupported selectors #}
<style>
@media (max-width: 600px) {
/* This will be dropped; add inline styles as fallback */
.content { font-size: 14px !important; }
}
</style>
Remote CSS Risks
remote: true (to fetch external CSS) introduces XSS risks if input isn’t sanitized.remote by default and sanitize all dynamic content:
// In your Twig extension config
$extension->setRemote(false);
Performance Overhead
php artisan emails:inline-css --templates=resources/views/emails --output=storage/emails
Twig-Specific Quirks
Selector Specificity Conflicts
!important sparingly or adjust your CSS to avoid conflicts.Inspect Inlined Output
Use Twig’s dump filter to debug:
{{ content | inline_css | raw | dump }}
Check for Dropped Selectors Compare the output with the original CSS to identify missing styles.
Log Warnings Extend the package to log dropped selectors:
// In your Twig extension
$inliner->setLogger(function ($message) {
\Log::warning('CSS Inliner: ' . $message);
});
Custom Inliner Configuration
Override the default CssInliner behavior:
$inliner = new \Twig\Extension\CssInlinerExtraExtension\CssInliner([
'preserve_media_queries' => false, // Default is false
'preserve_pseudo_elements' => false, // Default is false
]);
Pre/Post-Processing Hook into the inlining process to modify output:
$extension->setPostProcessor(function ($html) {
return str_replace('class="old-class"', 'class="new-class"', $html);
});
Fork and Maintain Given the package’s abandoned state, fork it on GitHub and:
Queue Inlining for Dynamic Emails Use Laravel Queues to avoid timeouts:
// In your Mailable
public function build()
{
return $this->markdown('emails.welcome')
->with(['content' => $this->content])
->queueOn('emails');
}
Use View Composers Automatically inline CSS for all email templates:
// app/View/Composers/EmailComposer.php
public function compose($view, array $data)
{
if (str_starts_with($view, 'emails.')) {
$data['content'] = $view->render() | inline_css;
}
}
**Environment
How can I help you explore Laravel packages today?