twig/html-extra
Twig HTML Extension adds handy helpers to Twig: a data_uri filter for RFC 2397 data URLs, an html_classes function to conditionally build CSS class strings, and an html_cva function for managing class variants via a Cva object.
Installation Require the package in your Laravel project:
composer require twig/html-extra
Register the extension in your Twig environment (e.g., in AppServiceProvider):
use Twig\Extra\Html\HtmlExtension;
public function boot()
{
$twig = $this->app['twig'];
$twig->addExtension(new HtmlExtension());
}
First Use Case
Use the html_classes function to conditionally merge classes in a Twig template:
<button class="{{ 'btn'|html_classes({
'btn-primary': isPrimary,
'btn-disabled': isDisabled,
'btn-lg': isLarge
}) }}">
{{ buttonText }}
</button>
Outputs:
<button class="btn btn-primary btn-lg">Submit</button>
Conditional Class Merging
Replace manual string concatenation with html_classes for cleaner templates:
{% set classes = 'card'|html_classes({
'card--featured': isFeatured,
'card--shadow': hasShadow,
'card--wide': isWide
}) %}
<div class="{{ classes }}">{{ content }}</div>
Class Variant Abstraction (CVA) Define reusable class variants for components (e.g., buttons, cards):
{% set button = 'button'|html_cva({
'primary': 'bg-blue-600 text-white',
'secondary': 'bg-gray-200 text-gray-800',
'size': {
'sm': 'px-3 py-1 text-sm',
'md': 'px-4 py-2',
'lg': 'px-6 py-3'
}
}) %}
<button class="{{ button('primary', 'md') }}">
Save
</button>
Data URIs for Embedded Assets Inline small assets (e.g., icons, SVGs) to reduce HTTP requests:
<img src="{{ 'assets/icons/logo.svg'|data_uri }}" alt="Logo">
Note: Validate file sizes (aim for <10KB) to avoid bloating HTML.
HTML Attribute Generation
Use html_attr (v3.24.0+) for dynamic attributes:
<input type="text" {{ html_attr({
'class': 'form-input',
'disabled': isDisabled,
'placeholder': 'Enter text...'
}) }}>
Component Libraries Pair with Laravel’s view components or Inertia.js for reusable UI:
{% component 'Button', {
'text': 'Click',
'classes': 'btn'|html_cva(buttonVariants)('primary', 'md')
} %}
Dark Mode Toggle Dynamically switch classes based on user preference:
{% set themeClasses = 'dark'|html_classes({
'light': isLightMode
}) %}
<body class="{{ themeClasses }}">
A/B Testing Randomly apply classes to test variants:
{% set variant = 'variant-a'|html_classes({
'variant-b': abTestVariant == 'b'
}) %}
<div class="{{ variant }}">{{ content }}</div>
Cache Twig Templates Pre-compile templates for production:
php artisan twig:cache:compile
Hybrid Blade/Twig Use Twig for dynamic HTML and Blade for Laravel-specific logic:
@twig('partials/_header.twig', {
'user': auth()->user(),
'activeRoute': request()->route()->getName()
})
Validation for Data URIs Sanitize inputs to prevent XSS:
// In a controller or form request
$path = Storage::path('uploads/' . $request->file('icon')->store('icons'));
$uri = e($path); // Escape for Twig
return view('template', ['iconUri' => $uri]);
Auto-escaping in Twig
Twig escapes HTML by default. Use |raw for trusted data_uri outputs:
<img src="{{ 'icon.svg'|data_uri|raw }}">
Immutable Cva Object
html_cva returns an immutable object. Reuse it or redefine:
{% set button = 'btn'|html_cva(buttonVariants) %}
{# ❌ Avoid: button('primary') = 'new-class' #}
{% set newButton = 'btn'|html_cva(buttonVariants)('primary') %}
Performance with Large Class Lists
html_classes iterates over all keys. For >50 classes, optimize:
{% set baseClasses = 'base-class1 base-class2' %}
<div class="{{ baseClasses ~ ' ' ~ (baseClasses|html_classes(additionalClasses)) }}">
Data URI Size Limits Browsers may reject large base64 strings. Test with:
wc -c <(base64 -w0 icon.svg)
Target: <10KB (adjust threshold based on use case).
Twig vs. Blade Conflicts
Avoid naming collisions (e.g., @class in Blade vs. html_classes in Twig). Use namespaces:
{% import '_partials/components.twig' as components %}
{{ components.button({ text: 'Click' }) }}
Check Twig Errors
Enable Twig’s debug mode in config/twig.php:
'debug' => env('APP_DEBUG', false),
Errors show template line numbers.
Inspect Cva Output
Debug variants with:
{{ dump(buttonVariants) }}
Validate Data URIs Test with a known file:
{{ 'assets/placeholder.png'|data_uri|raw }}
If broken, check file permissions or paths.
Extension Registration Ensure the extension is added after Twig is initialized:
$twig->addExtension(new HtmlExtension());
Custom Filters/Functions Extend the package by creating a custom Twig extension:
use Twig\TwigFunction;
$twig->addFunction(new TwigFunction('custom_html_class', function ($classes) {
return (new HtmlClasses())->merge(...$classes);
}));
Laravel Mix Compatibility
data_uri bypasses Laravel Mix. For dynamic assets:
{% if env('APP_ENV') == 'local' %}
<img src="{{ 'icon.svg'|data_uri|raw }}">
{% else %}
<img src="{{ mix('assets/icons/logo.svg') }}">
{% endif %}
Custom HtmlClasses Logic
Override the default merging behavior:
$twig->addFunction(new TwigFunction('custom_classes', function ($classes) {
$htmlClasses = new HtmlClasses();
$htmlClasses->setStrategy(new CustomStrategy());
return $htmlClasses->merge(...$classes);
}));
Data URI Sanitization Add a custom filter to validate paths:
$twig->addFilter(new TwigFilter('safe_data_uri', function ($path) {
if (!Storage::exists($path)) {
throw new \RuntimeException("File not found: {$path}");
}
return (new DataUri())->encode($path);
}));
Integrate with Tailwind/Jetstream
Use html_cva for Tailwind variants:
{% set button = 'focus:ring-4'|html_cva({
'primary': 'bg-blue-600 text-white',
'secondary': 'bg-gray-200 text-gray-800',
'size': {
'sm': 'px-3 py-1 text-sm',
'md': 'px-4 py-2'
}
}) %}
<button class="{{ button('primary', 'md') }}">
Login
</button>
resources/twig/variants/button.twig):
{% macro buttonVariants() %}
{
'primary': 'bg-blue-600 text-white',
'secondary': 'bg-gray-200 text
How can I help you explore Laravel packages today?