lemmon/twig-jsx

JSX-like component syntax for Twig

Maintainers

Package info

github.com/lemmon/twig-jsx

pkg:composer/lemmon/twig-jsx

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v0.1.0 2026-05-15 08:19 UTC

This package is auto-updated.

Last update: 2026-05-15 08:25:31 UTC


README

JSX-like component syntax for Twig.

License: MIT PHP Twig CI

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.

Requirements

  • PHP ^8.1
  • Twig ^3.0

Installation

composer require lemmon/twig-jsx

Quick Start

1. Register the Extension and Lexer

use Lemmon\TwigJsx\JSXPreLexer;
use Lemmon\TwigJsx\AttributeExtension;

$twig = new \Twig\Environment($loader);
$twig->addExtension(new AttributeExtension());
$twig->setLexer(new JSXPreLexer($twig));

2. Create a Component

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>

3. Use It in a Template

<!-- 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>

Prop Syntax

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

How a Component Reads Its Inputs

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 value
  • props.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

Configuration

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.

Alternatives

  • 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.

Contributing

Bug reports and pull requests are welcome on GitHub.

License

MIT. See LICENSE.