PHPackages                             lemmon/twig-jsx - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Templating &amp; Views](/categories/templating)
4. /
5. lemmon/twig-jsx

ActiveLibrary[Templating &amp; Views](/categories/templating)

lemmon/twig-jsx
===============

JSX-like component syntax for Twig

v0.1.0(3w ago)06MITPHPPHP ^8.1CI passing

Since May 15Pushed 1w agoCompare

[ Source](https://github.com/lemmon/twig-jsx)[ Packagist](https://packagist.org/packages/lemmon/twig-jsx)[ Docs](https://github.com/lemmon/twig-jsx)[ RSS](/packages/lemmon-twig-jsx/feed)WikiDiscussions main Synced 1w ago

READMEChangelogDependencies (4)Versions (2)Used By (0)

Twig JSX
========

[](#twig-jsx)

JSX-like component syntax for Twig.

[![License: MIT](https://camo.githubusercontent.com/7013272bd27ece47364536a221edb554cd69683b68a46fc0ee96881174c4214c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d626c75652e737667)](LICENSE)[![PHP](https://camo.githubusercontent.com/cc9cdea9aa96b40a822425e981b0a030e3371202973c7d57b74e8e99834f81dc/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f7068702d253545382e312d626c7565)](https://php.net)[![Twig](https://camo.githubusercontent.com/f72e4c3026dbd0d4ac9001818b5a672108eb473d902d2c64b64b82ef1a66ebf6/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f747769672d253545332e302d677265656e)](https://twig.symfony.com)[![CI](https://github.com/lemmon/twig-jsx/actions/workflows/ci.yml/badge.svg)](https://github.com/lemmon/twig-jsx/actions/workflows/ci.yml)

Write `` 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
------------

[](#requirements)

- PHP `^8.1`
- Twig `^3.0`

Installation
------------

[](#installation)

```
composer require lemmon/twig-jsx
```

Quick Start
-----------

[](#quick-start)

### 1. Register the Extension and Lexer

[](#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

[](#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.') %}

    {% if title %}{{ title }}{% endif %}
    {% block content %}{{ message }}{% endblock %}

```

### 3. Use It in a Template

[](#3-use-it-in-a-template)

```

    Wait! Something needs your attention.

```

Prop Syntax
-----------

[](#prop-syntax)

SyntaxExampleCompiles 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`A quoted value is a **static string** — it is not interpolated. For a dynamic value, use the expression form: `class={'alert-' ~ type}` rather than `class="alert-{{ type }}"`. A quoted value containing a `{{ … }}` output tag is rejected with a `SyntaxError` that points at the expression form, instead of silently passing the literal text. (To pass a literal `{{ … }}` through, wrap it in a quoted expression: `tpl={'{{ name }}'}`.) See [docs/decisions/0001-no-interpolation-in-quoted-props.md](docs/decisions/0001-no-interpolation-in-quoted-props.md)for the reasoning.

How a Component Reads Its Inputs
--------------------------------

[](#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 #}

    {{ message }}

```

- `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|spread }}` — renders all entries as HTML attribute pairs

Configuration
-------------

[](#configuration)

OptionDefaultDescription`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
------------

[](#alternatives)

- [Symfony UX Twig Component](https://symfony.com/bundles/ux-twig-component/current/index.html) — PHP class-backed components; requires the Symfony UX bundle.
- [TwigX](https://github.com/alma-oss/twigx-bundle) — similar `` syntax as a Symfony bundle; requires Symfony Config and DI.

Contributing
------------

[](#contributing)

Bug reports and pull requests are welcome on [GitHub](https://github.com/lemmon/twig-jsx).

License
-------

[](#license)

MIT. See [LICENSE](LICENSE).

###  Health Score

37

—

LowBetter than 81% of packages

Maintenance97

Actively maintained with recent releases

Popularity6

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity32

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

25d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/69d2fc0d66f2e8e99490f3781a67d2af0d6ca9cb3e48db4a6199336344d4310c?d=identicon)[lemmon](/maintainers/lemmon)

---

Top Contributors

[![lemmon](https://avatars.githubusercontent.com/u/251591?v=4)](https://github.com/lemmon "lemmon (15 commits)")

---

Tags

jsxphptemplatetwigtwigcomponentslexertemplatepreprocessorjsx

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/lemmon-twig-jsx/health.svg)

```
[![Health](https://phpackages.com/badges/lemmon-twig-jsx/health.svg)](https://phpackages.com/packages/lemmon-twig-jsx)
```

###  Alternatives

[symfony/ux-twig-component

Twig components for Symfony

21917.2M298](/packages/symfony-ux-twig-component)[timber/timber

Create WordPress themes with beautiful OOP code and the Twig Template Engine

5.7k3.6M126](/packages/timber-timber)[symfony/ux-live-component

Live components for Symfony

1636.5M111](/packages/symfony-ux-live-component)[twig/intl-extra

A Twig extension for Intl

36667.2M317](/packages/twig-intl-extra)[twig/cssinliner-extra

A Twig extension to allow inlining CSS

22919.7M79](/packages/twig-cssinliner-extra)[twig/inky-extra

A Twig extension for the inky email templating engine

16713.2M69](/packages/twig-inky-extra)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
