Summary
The JSX Theme Engine can reach a stable 2.0.0 for Drupal 11.3+ and 12 in five phases over about 9–12 months. Most of that time goes to one dependency: a core change that lets a non-Twig engine render module templates and SDC components.
Where the project stands (September 2026):
- Dormant since April 2024. 41 commits, a single
1.0.x-devbranch, no tagged release, no security advisory coverage, 47 issues (about 22 still open). The project is marked experimental and minimally maintained. Maintainers are bnjmnm, effulgentsia and hooroomoo, supported by Acquia. - Broken on 11.3+. It ships a legacy
jsx.enginefile, and 11.3 replaced that contract with tagged services. Canvas's sibling engine hit exactly this failure: "Service 'semi_coupled' not found" (canvas#3556101). - Its best ideas are already in Canvas. Canvas's semi-coupled theme engine "lifted relevant parts" of JSX (#3446434: Document "Semi-Coupled Theme Engine" and "Redux-integrated field widgets" components). Canvas is now removing that engine in favor of custom HTML tags inside Twig (canvas#3555062), which leaves JSX as the only effort aimed at a full non-Twig engine.
What stable means for this roadmap: a tagged service implementing ThemeEngineInterface; SDC components authored in JSX; server-rendered HTML with client hydration (no blank page before JS runs); full coverage of Drupal core's templates; opt-in to the security advisory policy; and tested support for Drupal 11.3, 11.4, 11.5 and 12.x.
The critical path is #3525011: Allow complete replacement of the default theme engine (twig) in Drupal core, "Allow complete replacement of the default theme engine (twig)". Until it lands, core forces Twig for module templates and SDC. The roadmap treats it as a hard blocker for SDC-to-JSX, drives it from inside core, and builds everything else in parallel.
Review of the current JSX project
The existing design is a sound proof of concept: Drupal emits custom elements with serialized props, and the browser maps them onto JSX components. It is not yet a theme engine in the production sense, because nothing renders without JavaScript.
Review basis: the Drupal.org project page, the 47-issue queue and discussion on merged merge requests. File-level details should be confirmed against the 1.0.x branch before estimates are finalized.
How it works today
Render array -> ThemeManager -> jsx.engine (legacy .engine file) -> <drupal-*> custom elements + serialized props -> Browser: hyperscriptify() -> React / Preact / Solid components mount
jsx.engineswaps each template for a custom element named after the hook, e.g.form-elementbecomes<drupal-form-element>, carrying the template variables as props (#3397049: Integrate with AJAX).- Each component's prop types are declared with PropTypes.
*.template-info.jsonfiles generated from them tell the engine which props a component expects (#3396749: Automate the generation of *.template-info.json files from the components' PropTypes declarations, #3396706: Add a way to discover possible props). - In the browser,
index-*.jsentry points callhyperscriptify(), which walks the DOM and builds a component tree for the chosen framework. - Preprocess logic moved from PHP into JS (#3392970: Move preprocess functions to JS code), and CSS moved from
attach_library()to CSS-in-JS (#3393003: Move CSS attachment from {{ attach_library() }} to a CSS-in-JS solution). - The Umami JSX demo theme ships React, Preact and Solid builds, plus a React debug tool showing template suggestions.
Strengths to keep
- Custom-element boundary. It lets JSX and Twig output coexist on one page, so the engine can be adopted one template at a time.
- Framework-agnostic mapping. Hyperscriptify targets React, Preact and Solid alike.
- Real Drupal UIs exercised. Layout Builder, AJAX, BigPipe and language switchers were all tested against it, and AJAX responses are re-hyperscriptified (#3397049: Integrate with AJAX).
- Props metadata anticipates SDC's
*.component.ymlschemas. - Lineage. Its approach was proven again in Canvas's semi-coupled engine.
Findings
| # | Finding | Severity | Evidence |
|---|---|---|---|
| 1 | Legacy jsx.engine contract; fails on 11.3+ exactly as Canvas's engine did |
Blocker | CR 3547356, canvas#3556101 |
| 2 | Client-only rendering: no HTML before JS runs, which hurts SEO, accessibility, no-JS users and Core Web Vitals | Blocker for stable | SSR only via an external Node app; #3393032: Add a server-side rendered (SSR or RSC) build for Umami JSX closed as outdated |
| 3 | Variables serialized to the client, which may expose data Twig would never print | High (security) | RSC was proposed partly to keep variables off the client |
| 4 | Prop type coercion unfinished; themes need Drupal internals knowledge | High | #3398446: Add type coercion logic to jsx.engine so that themes can declare prop types based on their needs, not on knowledge of Drupal internals Active |
| 5 | SDC integration is an initial implementation; SDC's single-directory rule conflicts with the app/ directory layout |
High | #3396976: Integrate with SDC (single directory components) |
| 6 | Partial AJAX: replaceWith and off-canvas submit buttons incomplete; nested React roots per AJAX response |
Medium | #3397049: Integrate with AJAX, #3432283: Layout Builder "Add Section" does not have submit button |
| 7 | Layout Builder gaps: Remove Section, breadcrumbs, contextual links | Medium | #3432278: Layout Builder "Remove Section" does not work in Umami JSX, #3432903: In Layout Builder the breadcrumbs are not showing up., #3432277: Get contextual links working in Layout Builder + Umami JSX |
| 8 | Template coverage limited to Umami; full-page conversion of 22 templates unfinished | Medium | #3396754: Convert all Twig templates to JSX components for a single URL |
| 9 | Preprocess and form_alter must be reimplemented in JS; no PHP escape hatch |
Medium | #3392970: Move preprocess functions to JS code, #3432503: Implement form_alter-esque functionality |
| 10 | No automated test suite; helper libraries not published to npm | Medium | #3432275: [META] Tests, #3393037: Add tests for Umami JSX, #3427734: Publish helper libraries to npm or jsr |
| 11 | Build tooling mid-migration to Vite; TypeScript types missing | Low | #3439774: Refactor jsx_umami to use Vite , #3396747: Add TypeScript declarations to the JSX component props |
| 12 | CSS-in-JS bypasses Drupal libraries, aggregation and libraries-override |
Low–medium | #3393003: Move CSS attachment from {{ attach_library() }} to a CSS-in-JS solution |
The existing npm toolkit (drupal-jsx org)
The rendering approach above is not just a design on paper: it already exists as eight small, single-purpose packages under the drupal-jsx GitHub organization, all type: module, MIT-licensed, last touched December 2023 — the same dormancy window as the main project. Phase 1 should revive and publish these rather than rebuild them from scratch.
| Package | Role | Depends on |
|---|---|---|
hyperscriptify |
Foundation. Converts a DOM tree into a hyperscript/JSX element tree, so a server-rendered <drupal-*> custom element becomes a real component. Also exports propsifyFactory() for attribute-to-prop conversion. |
none |
propsify |
Turns HTML attributes into component props: kebab-case to camelCase, JSON-parses stringified values, maps slots into nested prop paths. A strict build for React, a looser petite build for Preact/Solid. |
hyperscriptify, just-* helper libs |
serialize-prop-types |
Runs a component through a Web Worker to extract and serialize its PropTypes — the automation behind the *.template-info.json files (#3396749: Automate the generation of *.template-info.json files from the components' PropTypes declarations). |
prop-types (peer) |
drupal-utils |
Framework-agnostic main(): finds hyperscriptify-tagged elements on the page, converts attributes to props, mounts components. |
hyperscriptify |
drupal-utils-react |
React-specific wrapper: calls drupal-utils' main() with createElement/createRoot, and patches Drupal.attachBehaviors to run after React's render tick so Drupal's own JS doesn't race React. |
drupal-utils, propsify, React 18 (peer) |
drupal-utils-dev |
Dev-time helpers built on serialize-prop-types. |
drupal-utils, serialize-prop-types |
vite-plugin-drupal |
Vite plugin (Bun-powered): watches .jsx files, regenerates .template-info.json on change, invalidates Drupal's theme registry cache over a WebSocket — the live-reload story for theming with JSX. |
drupal-utils, drupal-utils-dev, Vite 5 (peer) |
starter-react |
The project scaffold: a starter Drupal theme wired to the engine, React and Vite, with a "JSX Devel" module for inspecting available props. README states it is experimental and not for production. | the packages above |
What this changes in the plan: @drupal-jsx/runtime (target architecture, below) is a revival of hyperscriptify + drupal-utils + drupal-utils-react, generalized to Preact as the default per the framework-scope decision, and @drupal-jsx/vite is a revival of vite-plugin-drupal + drupal-utils-dev. serialize-prop-types maps directly onto @drupal-jsx/types's job, but should be re-pointed at SDC's *.component.yml schemas instead of hand-authored PropTypes, per the SDC bridge in Phase 2. All of it is currently pinned to React 18 and Vite 5 peer dependencies, which need bumping as part of the 11.3+/12 compatibility pass.
Gap analysis: Drupal 11.3+ and Drupal 12
The project was written against Drupal 10's theme layer, and nearly every integration point it touches has moved since. Target core_version_requirement: ^11.3 || ^12 and drop Drupal 10, which reaches end of life on December 9, 2026.
Release timeline that sets the support matrix
| Release | Date | Implication for JSX |
|---|---|---|
| Drupal 11.3.0 | December 2025 | Engines become tagged services; .engine deprecated |
| Drupal 11.4.0 | Week of June 29, 2026 | Current 11.x minor; primary development target now |
| Drupal 11.5.0 / 12.0 beta 1 | Week of September 14, 2026 | 12.0 APIs are frozen enough to test against |
| Drupal 12.0.0 / 11.5.0 | Week of December 7, 2026 | 11.3 security support ends; first stable pair to certify |
| Drupal 10 EOL | December 9, 2026 | No reason to support it |
Source: core release schedule.
What must change
| Area | JSX today | Required on 11.3+ / 12 |
|---|---|---|
| Engine registration | jsx.engine file with jsx_*() functions |
Service JsxThemeEngine implements ThemeEngineInterface, tagged { name: theme_engine, engine_name: jsx }, provided by the jsx module |
| Template discovery | jsx_theme() + template-info JSON |
theme() returning drupal_find_theme_templates($existing, '.jsx', $path) or a custom scan that pairs .jsx files with metadata |
| Rendering | jsx_render_template() emitting custom elements |
renderTemplate() returning MarkupInterface; see architecture |
| Engine preprocess | Allowed pre-11.3 | Removed in 11.3; move to #[Hook('preprocess_HOOK')] classes in the module or theme |
| Hooks | Procedural .module/.theme functions |
OOP #[Hook] attribute classes, which themes support too |
| Theme config | engine: jsx in the theme |
Unchanged, but sub-themes inherit the root base theme's engine, so ship a jsx_base base theme |
| Theme debug | Custom React debug tool | Also emit THEME HOOK / suggestion comments server-side like TwigThemeEngine |
| Module templates | Rendered by Twig | Still forced to Twig: ThemeManager::render() sets $theme_engine = 'twig' for type: module |
| SDC | Initial bridge from 2023 | ComponentElement compiles to a Twig {% embed %} via inline_template, so SDC is Twig-only by construction |
| Canvas | Not considered | Canvas code components already use Preact and JSX; JSX engine components should be usable as Canvas components |
Carryover from the 2023 design that no longer fits
- Semi-coupled mapping JSON. Canvas is removing the same pattern because "few understand" it (canvas#3555062). Replace hand-maintained template-info JSON with SDC's
*.component.ymlprop schemas plus generated TypeScript types. - CSS-in-JS. Return to Drupal libraries (or SDC's automatic per-component CSS/JS) so aggregation,
libraries-overrideand asset bubbling work. - Framework trio. Supporting React, Preact and Solid triples the test matrix. Standardize on Preact with
preact/compatto align with Canvas, and treat React and Solid as community adapters.
The core blocker: Twig as default infrastructure
#3525011: Allow complete replacement of the default theme engine (twig) in Drupal core "Allow complete replacement of the default theme engine (twig) in Drupal core" is the gate for SDC-to-JSX, and it needs a broader scope and a reroll before it can land. It was opened May 16, 2025 by d34dman, is still Active, and its summary predates 11.3: it cites twig_render_template, which no longer exists.
Three couplings, three different fixes
Reading core main (12.0-dev, September 2026) shows that "Twig as default" is really three separate couplings. Only one of them is solvable in contrib today.
| Coupling | Core code | Can contrib work around it? | Needed in core |
|---|---|---|---|
| 1. Module templates fall back to Twig | ThemeManager::render(): if ($info['type'] != 'module') {...} else { $theme_engine = 'twig'; } |
Yes, partly. A template the JSX engine registers through theme() is stamped type: theme_engine, so it renders with JSX. Replicating every core template therefore works today; Twig only catches templates JSX has not ported. |
An engine-owned fallback policy, e.g. exclusive: true, so missing templates fail loudly instead of silently rendering Twig |
2. SDC templates must be .twig |
ComponentPluginManager::alterDefinition() calls findAsset($dir, $machine_name, 'twig') |
No. A card.jsx next to card.component.yml is never discovered |
Engine-aware template discovery; the definition records which engine owns the component |
| 3. SDC rendering is Twig by construction | ComponentElement builds {% embed 'provider:id' %} and renders it as #type => 'inline_template'; ComponentLoader is a Twig loader |
Only by replacing the element class via hook_element_info_alter(), which is fragile and breaks Twig-to-component include() |
Delegate component rendering to the owning engine through an optional engine interface |
inline_template itself, and Views rewrites, are Twig-language strings by definition. They should stay Twig and are not blockers.
Proposed core change set
File these as child issues of #3525011: Allow complete replacement of the default theme engine (twig) in Drupal core so each can land in its own minor release:
- Reroll #3525011: Allow complete replacement of the default theme engine (twig) in Drupal core on the 11.3 service architecture. Replace the hard-coded
'twig'with an engine-declared fallback. Add an optionalexclusiveflag that makes the active engine own module templates, throwing when a template is missing. - New issue: engine-agnostic SDC template discovery.
ComponentPluginManagerasks the tagged engines which extensions they own (.twig,.jsx,.tsx) and storesengineon the component definition. - New issue:
ComponentRenderingEngineInterface. An optional interface, e.g.renderComponent(ComponentPlugin $component, array $props, array $slots): array|MarkupInterface.ComponentElementdelegates to the owning engine; Twig keeps today'sinline_templatepath as its implementation. - New issue: cross-engine composition. Twig's
include('provider:card')must render a JSX-owned component (via a render array placeholder), and JSX must be able to render Twig-owned components as slots. This keeps mixed sites working during migration. - Test engine upgrade. Extend core's
nyan_cattest engine to own an SDC component and run in exclusive mode, so the new APIs stay covered in core CI.
Timing
New APIs land only in minor releases. 12.0 is in beta with release candidate 1 due the week of November 9, 2026, so the earliest realistic core target is a Drupal 12.x minor in 2027, with a backport to 11.x if the change is API-additive. The roadmap therefore ships a contrib-side SDC bridge first (Phase 2) and retires it once core lands (Phase 4).
Target architecture
Stable JSX renders HTML on the server and hydrates in the browser only where a component is interactive. SDC is the unit of authoring, and Drupal's render pipeline (caching, attachments, BigPipe) stays in charge. ThemeManager -> JsxThemeEngine::renderTemplate() -> PropNormalizer (Attribute, Markup, Url) -> Slots: nested render arrays via Renderer -> SSR renderer (Node sidecar or client-only) -> HTML + island markers -> Render cache / BigPipe -> Browser: hydrate interactive islands only
Components of the system
| Package | Type | Responsibility |
|---|---|---|
jsx |
Drupal module | JsxThemeEngine service; prop normalizer; SSR client; SDC bridge; server-side theme debug comments; Drush commands |
jsx_base |
Base theme (engine: jsx) |
JSX ports of all core templates, so sub-themes inherit a complete, Twig-free engine |
jsx_umami |
Demo theme | Rebuilt Umami JSX as the reference site and test fixture |
@drupal-jsx/runtime |
npm | Island hydration, Drupal.behaviors bridge, AJAX command handling (one root per island, not per AJAX response). Revives hyperscriptify + drupal-utils + drupal-utils-react |
@drupal-jsx/vite |
npm | Vite plugin: builds SSR and client bundles, emits Drupal library definitions. Revives vite-plugin-drupal + drupal-utils-dev |
@drupal-jsx/types |
npm | Generates TypeScript prop types from *.component.yml and theme-hook variables. Revives serialize-prop-types, re-pointed at SDC schemas instead of hand-authored PropTypes |
Rendering pipeline decisions
- Props are normalized in PHP, not JS.
Attributebecomes a plain object,MarkupInterfacea pre-escaped HTML string,TranslatableMarkupa string,Urlan href. This resolves #3398446: Add type coercion logic to jsx.engine so that themes can declare prop types based on their needs, not on knowledge of Drupal internals and replaces JS-side preprocess. - Render arrays become slots. Nested render arrays render through
RendererInterfacefirst, so#cacheand#attachedbubble exactly as with Twig. They arrive in JSX as a<Slot html={...} />. - SSR renderer is pluggable.
- Node sidecar (default for production): a long-lived Node/Bun process on a local socket renders the component; it is called only on render-cache misses.
- Client-only (development fallback): today's custom-element behavior, for hosts without a sidecar.
- Compile-to-PHP (research track): compile a static JSX subset to PHP at build time, removing the runtime dependency entirely.
- Islands, not whole-page hydration. Only components that export
hydrate = true(or use a'use client'directive) ship JS and serialized props. Server-only components never send variables to the browser, which closes the data-exposure finding. - Escaping. JSX autoescapes text; the only raw-HTML path is
<Slot>, which acceptsMarkupInterfaceoutput only. - Assets through Drupal libraries. The Vite plugin writes per-component libraries, so aggregation,
libraries-overrideand SDC's automatic CSS/JS keep working.
SDC in JSX
themes/custom/mytheme/components/card/ card.component.yml # props + slots schema (unchanged SDC format) card.tsx # default export: the component card.css # auto-attached, as with Twig SDC card.stories.tsx # optional import type { CardProps } from './card.types'; // generated from card.component.yml export default function Card({ heading, image, children }: CardProps) { return ( <article className="card"> {image} <h3>{heading}</h3> {children /* SDC slot */} </article> ); }
With core's engine-aware discovery (blocker items 2–3), card.tsx is discovered like card.twig. Placing it with #type => 'component', Layout Builder or Canvas then works unchanged. Until then, the jsx module's bridge registers these components under its own element type.
Roadmap
The roadmap has five phases that end in a 2.0.0 stable release, targeted for mid-2027. The core track (Phase C) runs in parallel from day one because it gates the stable release.
| Phase | Window | Release | Exit criteria |
|---|---|---|---|
| 0. Collaboration and triage | Oct 2026 | none | Roadmap agreed with the current maintainers; META published; queue triaged together |
| 1. Compatibility | Oct – Dec 2026 | 2.0.0-alpha1 (week of Dec 7, with Drupal 12.0) |
Engine runs as a tagged service on 11.3–12.0 with CI green |
| 2. SSR, islands and SDC bridge | Jan – Mar 2027 | 2.0.0-beta1 |
Server-rendered HTML; SDC components in .tsx via the contrib bridge |
| 3. Full template coverage | Mar – Jun 2027 | 2.0.0-rc1 |
jsx_base renders every front-end core template in exclusive mode |
| C. Core track (#3525011: Allow complete replacement of the default theme engine (twig) in Drupal core + children) | Oct 2026 – mid 2027 | Drupal 12.x minor | Engine-agnostic SDC discovery and rendering committed to core |
| 4. Stable | After C lands | 2.0.0 |
Bridge swapped for core API; security advisory coverage opted in |
The new work goes on a fresh 2.0.x branch. 1.0.x never had a release, so users see a clean line between the 2023 experiment and the supported engine.
Phase 0 — Collaboration and triage (October 2026)
- Share this roadmap with bnjmnm, effulgentsia and hooroomoo as a proposal, and refine it with their input on the original design goals.
- Offer to join as co-maintainer alongside the current team, and agree together on review and release responsibilities, especially for core-facing changes.
- Post a
[META] Roadmap to stableissue so the wider community can comment and contribute. - Review the queue with the maintainers: propose closing issues the new architecture supersedes (framework demos #3399991: Add a demo Fresh app and subtheme and #3396970: Integrate with Tome, JSON template-info work, CSS-in-JS follow-ups).
- Carry forward and retitle the issues that still apply: AJAX (#3432283: Layout Builder "Add Section" does not have submit button), Layout Builder (#3432278: Layout Builder "Remove Section" does not work in Umami JSX, #3432903: In Layout Builder the breadcrumbs are not showing up., #3432277: Get contextual links working in Layout Builder + Umami JSX), tests (#3432275: [META] Tests), npm publishing (#3427734: Publish helper libraries to npm or jsr), TypeScript (#3396747: Add TypeScript declarations to the JSX component props).
- With the maintainers' agreement, update the project status from experimental to "Under active development" and maintenance status to "Actively maintained".
Phase 1 — Compatibility (October – December 2026)
- Replace
jsx.enginewithJsxThemeEngine implements ThemeEngineInterface, taggedengine_name: jsx. - Move engine-level preprocess into
#[Hook]classes; convert.module/.themeprocedural hooks to OOP. - Set
core_version_requirement: ^11.3 || ^12and drop Drupal 10. - Adopt the Drupal Association GitLab CI template; test on 11.3, 11.4, 11.5 and 12.0 (beta, then RC, then release), with
next majorruns. - Finish the Vite migration (#3439774: Refactor jsx_umami to use Vite ) by reviving
vite-plugin-drupal; standardize on Preact withpreact/compat. - Audit the eight dormant
drupal-jsxnpm packages (hyperscriptify,propsify,serialize-prop-types,drupal-utils,drupal-utils-react,drupal-utils-dev,vite-plugin-drupal,starter-react): bump their React 18 / Vite 5 peer dependencies, decide on a monorepo, and publish the revived@drupal-jsx/runtimeand@drupal-jsx/viteto npm (#3427734: Publish helper libraries to npm or jsr). - Rebuild
jsx_umamion the new engine as the smoke test.
Phase 2 — SSR, islands and the SDC bridge (January – March 2027)
- Build the PHP prop normalizer and slot rendering through
RendererInterface. - Build the Node sidecar SSR renderer and a DDEV add-on, plus a client-only fallback mode.
- Add island hydration with opt-in
hydrate, and stop serializing props for server-only components. - Rework AJAX to one root per island; fix
replaceWithand off-canvas submit buttons. - Ship the contrib SDC bridge: discover
*.tsxbeside*.component.yml, validate props against the schema, render via the engine. - Build
@drupal-jsx/typesto generate TypeScript prop types fromcomponent.yml. - Emit server-side theme debug comments matching Twig's format.
Phase 3 — Full template coverage (March – June 2027)
Core main contains 263 non-test *.html.twig templates across its modules; system alone has 72. Admin-only templates (Views UI, Field UI, Help, Navigation) can stay on the admin theme's Twig, which leaves roughly half for jsx_base.
- Generate a coverage report: every theme hook versus the templates
jsx_baseprovides. - Port front-end templates in priority order: page/region/block, node/field/entity, forms and form elements, Views output, media/image/responsive image, menus, comments, user.
- Add a Drush generator,
drush jsx:generate-theme, that clonesjsx_basethe waystarterkit_themedoes for Twig. - Port the relevant core theme tests plus Layout Builder, AJAX and BigPipe tests (#3435995: Port Layout Builder tests to umami JSX , #3432275: [META] Tests).
- Run accessibility (axe) and performance budgets (TTFB and hydration cost) against Olivero-on-Twig as the baseline.
Phase C — Core track (parallel, October 2026 – mid 2027)
- Reroll #3525011: Allow complete replacement of the default theme engine (twig) in Drupal core against the 11.3 service architecture, adding an engine-declared fallback and an
exclusivemode. - File and drive the child issues: engine-agnostic SDC discovery;
ComponentRenderingEngineInterface; cross-engineinclude(); an SDC-capablenyan_cattest engine. - Present the plan at a DrupalCon or camp session and at a core committer office hour to get framework-manager sign-off early.
- Coordinate with Canvas maintainers so JSX-owned SDC components appear in Canvas's component library.
Phase 4 — Stable
2.0.0 ships when these gates are all met:
- The core SDC APIs from Phase C are in a released Drupal minor, and the contrib bridge becomes a shim for older 11.x versions.
- No open critical or major bugs; test coverage includes every ported template.
- Security advisory coverage opted in, which requires a stable release and a vetted maintainer.
- Documentation covers the theming guide, SDC-in-JSX guide, migrating a Twig theme, and SSR hosting.
Risks and open questions
The largest risk is that the core track moves slower than the contrib work. The mitigation is a contrib bridge that is good enough to ship a beta without core changes.
| Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|
| #3525011: Allow complete replacement of the default theme engine (twig) in Drupal core and its SDC children miss the 2027 minors | Medium | Stable slips | Contrib SDC bridge in Phase 2; get framework-manager buy-in in Phase C before writing patches |
| Core prefers Canvas's direction (custom tags inside Twig) over non-Twig engines | Medium | Core APIs rejected | Frame the APIs as engine-neutral, proven by nyan_cat, and useful to any engine rather than JSX-specific |
| SSR sidecar is a hosting burden (Pantheon, Acquia, shared hosts) | High | Adoption | Client-only fallback; research compile-to-PHP; document supported hosts |
| Porting ~130 front-end templates is slow | Medium | RC slips | Coverage report; generator scaffolding; recruit front-end contributors per template group |
| Contrib modules ship Twig templates and Twig extensions | High | Mixed-engine sites | Non-exclusive mode stays the default; per-module JSX template packs are community work |
| Maintainer time is limited across the team | Low–medium | Slower reviews | Spread review and release duties across co-maintainers so no one person is a bottleneck |
| Security coverage review | Low | Delays stable | Start the security opt-in process during the RC |
Open questions:
- Framework scope: Preact-only for stable, with React and Solid as community adapters, or keep all three first-class?
- Branch: start
2.0.x, or continue1.0.xbecause it was never released? - SSR default: is a Node sidecar acceptable as the production default, or must client-only be the default?
- Canvas: should JSX SDC components be registered as Canvas "code components", or only as SDC components that Canvas already understands?
Sources
- JSX Theme Engine project page and issue queue
- drupal-jsx GitHub organization:
hyperscriptify,propsify,serialize-prop-types,drupal-utils,drupal-utils-react,drupal-utils-dev,vite-plugin-drupal,starter-react - JSX issues: #3397049: Integrate with AJAX, #3396749: Automate the generation of *.template-info.json files from the components' PropTypes declarations, #3393032: Add a server-side rendered (SSR or RSC) build for Umami JSX, #3396976: Integrate with SDC (single directory components), #3398446: Add type coercion logic to jsx.engine so that themes can declare prop types based on their needs, not on knowledge of Drupal internals, #3396754: Convert all Twig templates to JSX components for a single URL
- Core: #3525011: Allow complete replacement of the default theme engine (twig) in Drupal core and #1685492: Convert theme engines into services, plus change record 3547356
- Experience Builder: #3446434: Document "Semi-Coupled Theme Engine" and "Redux-integrated field widgets" components
- Canvas #3556101: semi-coupled engine broken on 11.3 and Canvas #3555062: removing the engine
- Drupal core release schedule
- Drupal core source,
mainbranch (12.0-dev, September 18, 2026):ThemeManager.php,ComponentPluginManager.php,ComponentElement.php,TwigThemeEngine.php,ThemeEngineInterface.php, and template counts fromcore/modules
AI disclosure
The author used Claude Code throughout authoring this plan, with human review and decisions made independently, per Drupal.org's AI contribution policy.
| Comment | File | Size | Author |
|---|---|---|---|
| JSX-Theme-Engine.png | 41.63 KB | matthand |
Comments
Comment #2
matthandComment #3
matthand