fs-project layout
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 addfontLoader()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 rawhtmlwidgets orraw=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 orbox({...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. Usecollect-submissions, notemail, on Elementor 4.2.x with Pro 4.1.0 (the email action is upstream-broken there). Submissions land inwp_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)
Built-in names shadow yours
Built-in names shadow yours
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 stretch unless width-pinned
Row children stretch unless width-pinned
Row children get
flex: 1 unless width-pinned. Use w: 'hug' for
justify-between clusters.Absolute overlays in flex parents render 0x0
Absolute overlays in flex parents render 0x0
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'.Class-only display:grid loses to atomic flex CSS
Class-only display:grid loses to atomic flex CSS
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 in rotated cards collapses
Text in rotated cards collapses
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 in flex columns overflow mobile
Headings in flex columns overflow mobile
Headings inside flex columns need
w: '100%' or they shrink to max-content
and overflow on mobile.Build width-general
Build width-general
Absolutes as
left: calc(50% +/- Npx), never fixed left: Npx; grids
centered. Pages get viewed at 1200 through 2560.Absolute img inside an html widget gets clamped
Absolute img inside an html widget gets clamped
An absolute
<img> inside an html widget needs max-width: none; the
theme clamps it to the wrapper width.backdrop-filter traps fixed overlays
backdrop-filter traps fixed overlays
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 }).Nowrap text never wraps anywhere
Nowrap text never wraps anywhere
Mono or
white-space: nowrap text wraps nowhere; cap or wrap it for
390px.Image URLs
Image URLs
Absolute http(s) on the target site, single query param.
Marquees and bleed decorations
Marquees and bleed decorations
raw="overflow:hidden;" on the section, or the bleed widens the page.Dark themes: verify contrast in screenshots
Dark themes: verify contrast in screenshots
Body text at roughly
#b0b8c0 or lighter on near-black; judge contrast in
screenshots, not by hex.