Skip to main content
This is the complete authoring surface of elementor-jsx on one page. Every name here is real and tested. It is also available offline, always current with your installed version, via:

fs-project layout

Build and deploy: exjsx build <dir>, then exjsx deploy <bundle.json> (idempotent, self-primes CSS, detects the Elementor version over REST, no wp-cli needed). exjsx lint <dir> before every deploy. Details: Projects, Deploy.

Intrinsics: the only tags

No nav, main, ul, span, a, or button intrinsics. Container tag override: tag="header|footer|article|aside|a|button". Special props on all intrinsics: tw="" (Tailwind subset), raw="" (CSS declarations, auto-terminated), cls="name" (semantic class label), gcls="name" (arbitrary extra class, style it yourself), id="anchor" (real HTML id, href="#anchor" works), animate={{effect: 'fade'|'slide'|'scale', trigger: 'load'|'scrollIn'}}.

sx style props

Kit helpers (free vars via the prelude, no imports)

  • Fonts load natively: any Google font named in a style prop is enqueued by Elementor itself on render (elementor-gf-*). Do not add fontLoader() for those (it double-loads). fontLoader('Family', [400, 700]) (first in the tree, one per family) is only for families Elementor cannot see: fonts referenced solely inside raw html widgets or raw= CSS.
  • navBar({logo, links: [[label, href], ...], ctas, accent, ink}): a complete header (desktop rail, dropdown mega-menus, mobile hamburger) as one self-contained html widget, immune to the burger flex-slot bug by construction. Start here for navs.
  • button(text, href, envelopeProps?): real href required (no '#'). The third argument takes atomic envelopes only (plain sx values throw, with recipes). For styled CTAs prefer a styled <text href> anchor or box({...sx, tag: 'a'}).
  • divider(props) · tabs([{label, content}], {active}) · youtube(url) · video(url)
  • Forms (Pro), the complete recipe, all four parts required:
    Input types: text, email, number, tel, password, or textarea: true. There is no radio widget in atomic Pro forms: option-picker UIs are styled checkbox fields (one per option, distinct ids) or a select. Use collect-submissions, not email, on Elementor 4.2.x with Pro 4.1.0 (the email action is upstream-broken there). Submissions land in wp_e_submissions.
  • formSuccess({message, sub, accent}): canned success banner; hides the form and shows the banner when the Pro ajax submit succeeds. One per page with a form. The atomic runner shows no sent-state on its own.

Media manifest

data/media.manifest.mjs, run exjsx media <manifest>: default-export an array of {slot, file: '/abs/or/rel.jpg'} or {slot, src: 'https://...'} (fonts: {slot, type: 'font', file, family, role, weight}, embedded as a data-URI). Idempotent by slot; writes data/media-map.json. Read ids and URLs from the map, never hardcode. Full contract: Media and fonts.

Layout gotchas (each cost a real run)

The prelude provides Nav, Footer, and Layout as built-in free vars; a project component named Nav gets silently shadowed. Name yours SiteNav and SiteFooter and import them explicitly.
Row children get flex: 1 unless width-pinned. Use w: 'hug' for justify-between clusters.
pos: 'absolute' plus raw="inset:0;" inside a flex parent renders zero by zero (no width or height of its own). Give the overlay explicit w="100%" h="100%" and the parent pos: 'relative'.
A bare <html> widget as the third child of a justify="space-between" header still occupies the right flex slot even when its inner button is display: none: the links rail gets centered, not right-pinned (invisible at 1200, glaring at 1512 and wider; this broke 8 of 10 batch sites). Wrap it: <box w="hug" pad={0} display="none" mobile={{display:'flex'}}><html raw={...}/></box>.
Elementor prints its atomic flex CSS later in the body, so a grid set only in a class loses. Set display and grid props via sx (atomic); keep only extras (auto-rows, dense, bleed) in raw.
Text inside rotated or transformed cards needs explicit width (w: '100%' inner, fixed card width); hug-width paragraphs in tilted containers collapse to one word per line.
Headings inside flex columns need w: '100%' or they shrink to max-content and overflow on mobile.
Absolutes as left: calc(50% +/- Npx), never fixed left: Npx; grids centered. Pages get viewed at 1200 through 2560.
An absolute <img> inside an html widget needs max-width: none; the theme clamps it to the wrapper width.
backdrop-filter on a nav creates a containing block, so position: fixed overlays inside get trapped. Toggle it off when the overlay opens (.open { backdrop-filter: none }).
Mono or white-space: nowrap text wraps nowhere; cap or wrap it for 390px.
Absolute http(s) on the target site, single query param.
raw="overflow:hidden;" on the section, or the bleed widens the page.
Body text at roughly #b0b8c0 or lighter on near-black; judge contrast in screenshots, not by hex.

Verify and test (studio commands, never hand-written Playwright)

Full reference: Verify and Measure.