codeschubser/twig-components-bundle
Install the Bundle
composer require codeschubser/twig-components-bundle
Register the bundle in config/bundles.php:
return [
// ...
Codeschubser\TwigComponentsBundle\TwigComponentsBundle::class => ['all' => true],
];
Enable Twig Components
In config/packages/twig.yaml, ensure Twig is configured to use components:
twig:
components:
enabled: true
First Use Case: Render a Bootstrap Button
Create a Twig component template at templates/components/button.html.twig:
{# templates/components/button.html.twig #}
<button
type="{{ type ?? 'button' }}"
class="btn {{ classes ?? 'btn-primary' }}"
{% if disabled %}disabled{% endif %}
>
{{ label ?? 'Click Me' }}
</button>
Use it in a template:
{% import _self as components %}
{{ components.button({ label: 'Submit', classes: 'btn-success' }) }}
Directory Structure:
Group components by feature (e.g., templates/components/auth/, templates/components/ui/).
Example:
templates/
├── components/
│ ├── auth/
│ │ ├── login-form.html.twig
│ ├── ui/
│ │ ├── alert.html.twig
│ │ ├── modal.html.twig
Reusable Components:
Use {% import %} to share components across templates:
{% import 'components/_shared.html.twig' as shared %}
{{ shared.card({ title: 'User Profile' }) }}
Passing Attributes:
Use attributes to pass HTML attributes dynamically:
{{ components.input({
type: 'email',
attributes: { placeholder: 'user@example.com', required: true }
}) }}
Slot-Based Layouts:
Create flexible components with slots (e.g., header, body, footer):
{# templates/components/card.html.twig #}
<div class="card">
<div class="card-header">{{ header|default('Default Header') }}</div>
<div class="card-body">{{ body }}</div>
<div class="card-footer">{{ footer }}</div>
</div>
Usage:
{{ components.card({
header: 'Profile',
body: 'User details go here',
footer: 'Updated: ' ~ now|date('Y-m-d')
}) }}
templates/components/form/input.html.twig):
<div class="form-group">
<label for="{{ id }}">{{ label }}</label>
{{ form_widget(form) }}
{% if errors|length > 0 %}
<div class="text-danger">{{ errors|first }}</div>
{% endif %}
</div>
Use it in a controller:
return $this->render('page.html.twig', [
'form' => $form->createView(),
]);
In Twig:
{% import 'components/form/input.html.twig' as form %}
{{ form.input({
form: form.email,
label: 'Email Address'
}) }}
icon parameter:
{# templates/components/button.html.twig #}
<button class="btn {{ classes }}">
{% if icon %}
<i class="{{ icon }} me-2"></i>
{% endif %}
{{ label }}
</button>
Usage:
{{ components.button({
label: 'Save',
classes: 'btn-outline-primary',
icon: 'bi bi-save' // Bootstrap Icons
}) }}
Component Not Found:
Ensure the template path is correct and the import statement points to the right location.
Example error: TemplateNotFoundException → Verify templates/components/ exists.
Caching Issues: Clear Twig cache after adding new components:
php bin/console cache:clear
Disable Components Globally:
Set enabled: false in twig.yaml to disable all components (useful during development).
Override Defaults:
Use null to reset defaults in child components:
{{ components.button({ label: null, classes: 'btn-link' }) }}
Custom Components:
Extend existing components by creating new templates that {% extends %} base components.
Example:
{# templates/components/extended-button.html.twig #}
{% extends 'components/button.html.twig' %}
{% block button_classes %}
{{ parent() }} btn-lg
{% endblock %}
Dynamic Component Loading:
Use {% component %} tag (if supported in future versions) for runtime component resolution:
{% component 'components/' ~ dynamicName ~ '.html.twig' %}
Hidden Icons:
Ensure icons are hidden from screen readers by adding aria-hidden="true":
<i class="{{ icon }}" aria-hidden="true"></i>
Form Labels:
Always include <label> for form inputs to maintain accessibility:
<label for="{{ id }}">{{ label }}</label>
Component Caching: Cache frequently used components in Symfony’s HTTP cache:
# config/packages/framework.yaml
framework:
http_cache:
cache_control:
rules:
- path: ^/components/
max_age: 3600
Minimize Imports:
Avoid deep nesting of {% import %} statements to reduce template lookup time.
How can I help you explore Laravel packages today?