JSX-like component syntax for Twig.
Write <Alert {type} important /> instead of a verbose {% include %} call. Twig JSX transforms JSX-like component tags into native Twig {% include %} and {% embed %} calls at the lexer level — no runtime overhead, no Symfony dependency.
- PHP
^8.1 - Twig
^3.0
composer require lemmon/twig-jsxuse Lemmon\TwigJsx\JSXPreLexer;
use Lemmon\TwigJsx\AttributeExtension;
$twig = new \Twig\Environment($loader);
$twig->addExtension(new AttributeExtension());
$twig->setLexer(new JSXPreLexer($twig));Save your component in templates/components/Alert.twig:
{% set type = props.type|default('info') %}
{% set important = props.important|default(false) %}
{% set title = props.title|default(null) %}
{% set message = props.message|default('No message provided.') %}
<div class="alert alert-{{ type }}{% if important %} alert-important{% endif %}{% if props.class|default('') %} {{ props.class }}{% endif %}"
{{ props.except('type', 'important', 'title', 'message', 'class')|render }}>
{% if title %}<strong>{{ title }}</strong>{% endif %}
{% block content %}{{ message }}{% endblock %}
</div><!-- Self-closing with shorthand -->
<Alert {type} important message="Everything is great!" />
<!-- With children and extra attributes -->
<Alert type="warning" class="shadow-lg" data-id="123">
<strong>Wait!</strong> Something needs your attention.
</Alert>| Syntax | Example | Compiles to (inside props bag) |
|---|---|---|
| Static | type="info" |
'type': 'info' |
| Expression | type={userType} |
'type': userType |
| Expression | count={items|length} |
'count': items|length |
| Expression | theme={dark ? 'd' : 'l'} |
'theme': dark ? 'd' : 'l' |
| Shorthand | {type} |
'type': type |
| Boolean | important |
'important': true |
Every prop the caller passes — semantic inputs and HTML attributes alike — arrives in a single
props bag of type ComponentAttributes. The component template decides what to extract:
{# Destructure semantic inputs as locals #}
{% set type = props.type|default('info') %}
{% set message = props.message|default('') %}
{# Spread the remaining keys as HTML attributes #}
<div class="alert-{{ type }}" {{ props.except('type', 'message')|render }}>
{{ message }}
</div>props.key— read any valueprops.except('a', 'b', ...)— returns a new bag without the listed keys; useful for spreading HTML fallthrough attributes onto the root element{{ props|render }}— renders all entries as HTML attribute pairs
| Option | Default | Description |
|---|---|---|
directory |
components |
Subdirectory inside templates/ where component files are looked up. |
extension |
.twig |
File extension for component templates. |
prefix |
"" |
Tag prefix. When empty, any Capitalized tag is treated as a component (JSX-style). |
props_variable |
props |
Name of the variable that holds all props in the component template. |
content_block |
content |
Twig block name where a bodied tag's children are rendered. |
- Symfony UX Twig Component — PHP class-backed components; requires the Symfony UX bundle.
- TwigX — similar
<Component />syntax as a Symfony bundle; requires Symfony Config and DI.
Bug reports and pull requests are welcome on GitHub.
MIT. See LICENSE.