Intrinsics: the only tags
There are no
nav, main, ul, span, a, or button intrinsics; the
compile error tells you this, believe it. Two rules cover the gaps:
- Links and buttons are
<text href>or<heading href>; they render real anchors. A barehref="#"is a placeholder: wire it or use a#sectionanchor (lint:placeholder-link). - Semantic containers come from the
tag=override on any container:tag="header|footer|article|aside|a|button".
tw, raw, cls, gcls (see
Styling), id="anchor" (a real HTML id, so href="#anchor"
works), and animate={{effect: 'fade'|'slide'|'scale', trigger: 'load'|'scrollIn'}}.
Text intrinsics accept only <em>, <strong>, and <br> inline children;
anything else throws at compile rather than silently dropping content. Exactly
one <h1> per page, and heading levels step one at a time (lint:
heading-structure).
Kit helpers
Kit helpers are free variables via the prelude; no imports needed. They return kit nodes you can mix freely with JSX children.navBar: the safe header
Forms (Elementor Pro): the complete recipe
All four parts are required:- Input types:
text,email,number,tel,password, ortextarea: 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, notemail, on Elementor 4.2.x with Pro 4.1.0: the email action is upstream-broken there (the validator and the send-runner disagree). Submissions land in thewp_e_submissionstable, visible under Elementor, then Submissions in wp-admin. - Add
formSuccess({ message, sub, accent })anywhere on the page: it hides the form and shows a success banner when the Pro ajax submit succeeds. The atomic runner shows no sent-state on its own. One per page with a form.
button: the envelope rule
href is required (no '#'). The third argument takes atomic
envelopes only; plain sx values throw, and the error carries recipes. For
styled CTAs, prefer a styled <text href> anchor or box({...sx, tag: 'a'}).
Embeds and structure
divider(props)tabs([{label, content}, ...], {active})youtube(url)andvideo(url)
fontLoader: narrow scope
Google fonts named in a style prop (font= or an sx font-family) load
natively: Elementor enqueues an elementor-gf-* stylesheet on render. Do
not add fontLoader() for those; it double-loads.
fontLoader('Family', [400, 700]) (placed first in the tree, one per family) is
only for families Elementor cannot see: fonts referenced solely inside raw
html widgets or raw= CSS. Without it those fall back silently and every
measurement you take is wrong (lint: font-not-loaded). Custom font files go
through the media manifest.
Next
Media and fonts
The manifest contract for images and font files.
Troubleshooting
The layout gotchas each of these rules prevents.