Installation:
composer require almacareer/twigx-bundle
For Symfony Flex projects, the bundle auto-registers. Otherwise, add to bundles.php:
AlmaCareer\TwigXBundle\TwigXBundle::class => ['all' => true],
First Use Case:
Create a Twig component template (e.g., templates/components/Button.twig) and use JSX-like syntax in your template:
<Button type="primary" onClick={handleClick}>Click Me</Button>
The bundle maps this to:
{% embed "@spirit/Button.twig" with { props: { type: 'primary', onClick: handleClick } } %}
Click Me
{% endembed %}
Default Paths:
Components are resolved from %kernel.project_dir%/templates/components by default. Override via config/packages/twigx.yml:
twigx:
paths:
- "%kernel.project_dir%/templates/custom-components"
paths_alias: 'custom-ui'
JSX-like Syntax:
<Alert type="success" message={user.message} />
<Link href="/dashboard" class="btn">Go to Dashboard</Link>
Supports:
type="primary", href="/path", class="btn".{variable}, {{ ternary ? 'yes' : 'no' }}.isOpen, isDisabled={false}.<ButtonLink />.Fallback to Twig:
{% embed "@custom-ui/Button.twig" with { props: { text: 'Click' } } %}
{{ props.text }}
{% endembed %}
templates/
├── components/
│ ├── Button.twig
│ ├── Alert.twig
│ └── ...
└── pages/
└── home.twig
Use paths in twigx.yml to include multiple directories:
twigx:
paths:
- "%kernel.project_dir%/templates/components"
- "%kernel.project_dir%/templates/vendor-components"
JSX Syntax:
<TextField
label="Username"
value={user.username}
onChange={handleChange}
/>
Maps to Twig props:
{% set props = {
label: 'Username',
value: user.username,
onChange: handleChange
} %}
Twig Syntax (Mixed):
<Component attr={{ dynamicValue }} other={staticValue} />
<FormField
label="Email"
type="email"
value={form.email.value}
errors={form.email.errors}
onChange={form.email.onChange}
/>
Render the form field as a Twig component with props for validation errors.<Button
type={isAdmin ? 'admin' : 'user'}
disabled={!isActive}
>
{isAdmin ? 'Admin Panel' : 'User Dashboard'}
</Button>
CustomButton.twig) and reference it via paths_alias:
twigx:
paths:
- "%kernel.project_dir%/templates/custom-components"
paths_alias: 'custom'
Use in templates:
<custom:CustomButton size="large">Submit</custom:CustomButton>
Case Sensitivity:
<Button> not <button>).Button.twig for <Button>).Attribute Parsing:
kebab-case (e.g., data-test="value").
❌ Fails: dataTest="value".{} or {{}} (e.g., { value } works, but {{ value }} may fail).Boolean Props:
is* prefix for booleans (e.g., isDisabled={true}).true (e.g., <Button isOpen>).Twig Comments:
{/* comment */ value}) are stripped during parsing.{% comment %}.Self-Closing Tags:
/> is used for void components (e.g., <ButtonLink />).<ButtonLink />Text will fail).Configuration Overrides:
paths_alias is not set, the default alias (spirit) is used.* for wildcards (e.g., templates/components/*/Button.twig).Check Parsed Props:
Enable Twig debug mode (APP_DEBUG=true) to inspect how props are rendered in the final HTML.
Component Not Found:
paths.Syntax Errors:
{{ }} or {} separately (e.g., test {{ variable }} in a plain Twig template first).Caching Issues: Clear Symfony cache after adding new components:
php bin/console cache:clear
Custom Lexer/Parser:
Extend the bundle’s TwigXLexer or TwigXParser to support additional syntax (e.g., custom attributes).
Component Inheritance:
Use Twig’s {% extends %} in component templates to share logic:
{# templates/components/BaseButton.twig #}
<button class="base-button {{ props.class }}">
{% block content %}{% endblock %}
</button>
{# templates/components/PrimaryButton.twig #}
{% extends "@custom-ui/BaseButton.twig" %}
{% block content %}{{ props.children }}{% endblock %}
Dynamic Component Loading:
Load components dynamically via twigx.yml paths or use Symfony’s ContainerAware interfaces to fetch paths at runtime.
Integration with Stimulus: Pass Stimulus controllers as props:
<Modal isOpen={isModalOpen} controller="modal" controllerParams={{ { target: 'modal' } }}
Content
Component Caching:
The bundle leverages Twig’s built-in caching. Ensure your twig.cache_warmer is configured for optimal performance.
Avoid Over-Embedding: Limit nested components to reduce template complexity and improve render times.
Static Props:
Prefer static props (e.g., type="primary") over dynamic ones (e.g., {type}) where possible to minimize runtime processing.
Slot Content:
Use Twig’s {{ content() }} in component templates to support slot-like content:
{# templates/components/Card.twig #}
<div class="card">
<div class="card-header">{{ props.header }}</div>
<div class="card-body">{{ content() }}</div>
</div>
Usage:
<Card header="Title">
<p>Card content</p>
</Card>
Component Events:
Dispatch custom events via props (e.g., onCustomEvent={handleEvent}) and handle them in JavaScript:
<Component onCustomEvent={dispatchEvent} />
document.querySelector('[data-component]').addEventListener('customEvent', (e) => {
console.log(e.detail);
});
TypeScript Support:
Generate .d.ts files for IDE autocompletion by parsing your component templates (requires custom tooling). Example:
interface ButtonProps {
type
How can I help you explore Laravel packages today?