> ## Documentation Index
> Fetch the complete documentation index at: https://docs.wpos.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# API card

> The condensed one-page authoring reference: intrinsics, sx keys, kit helpers, and the gotcha library.

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:

```bash theme={null}
npx exjsx api
```

## fs-project layout

```text theme={null}
site/
  site.config.mjs        export default { name: 'mysite' }
  theme.mjs              export default defineTheme({ mode:'literal', colors:{...}, fonts:{...} })
  components/*.jsx       shared components (import-free: the prelude provides the API as free vars)
  pages/home.page.jsx    export const meta = { title, slug?, seo?, template? }
                         export default ({ theme }) => <section>...</section>
```

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](/ultra/projects), [Deploy](/ultra/deploy).

## Intrinsics: the only tags

| Tag                               | Notes                                                                                    |
| --------------------------------- | ---------------------------------------------------------------------------------------- |
| `box` `div` `col` `row` `section` | Flex containers. `row` forces dir row; `section` renders `<section>`                     |
| `h1` `h2` `h3` `h4` / `heading`   | Text props apply; inline `<em>`, `<strong>`, `<br>` children OK                          |
| `text` `p`                        | Paragraph; `href` renders a real anchor (use for links and buttons-as-links)             |
| `img`                             | `src` = URL string (inline `alt` OK) or attachment id (alt comes from the media library) |
| `html`                            | Raw HTML/SVG/style/script carrier: `raw` prop or children                                |

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

| Group      | Keys                                             | Rules                                                                                                                                                                                                                                              |
| ---------- | ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Sizes      | `w h maxw minh gap size radius pad m`            | Number = px, or a `'N<unit>'` string with px, %, em, rem, vw, vh, ch (units honored). `w h` also take `'hug'` / `'auto'`. `calc()`, `clamp()`, keywords, two-value gap all **throw**: put them in `raw=`                                           |
| Box        | `pad m`                                          | Also take `[v,h]`, `[t,r,b,l]`, partial `{t,r,b,l}`, `'0 auto'` strings                                                                                                                                                                            |
| Flex       | `dir align justify wrap center display flex pos` | `dir` is `'row'` or `'column'` (`'col'` throws). `align`/`justify` validated against Elementor's enums; shorthands `'between'`/`'around'`/`'evenly'` auto-map; **no baseline**, use `flex-end`                                                     |
| Grid       | `span gridCols`                                  | `gridCols` e.g. `'repeat(3, 360px)'`; add `raw="justify-content:center;"` or grids left-pin                                                                                                                                                        |
| Color      | `color bg`                                       | Hex, **not** gradient strings                                                                                                                                                                                                                      |
| Background | `bgImage bgOpts grad`                            | `bgImage` url or id; `bgOpts` `{color, size:'cover', position:'center center', repeat:'no-repeat', attachment}`; `grad` is an **array** `[angle, from, to]` (a CSS gradient string throws; freeform gradients go in `raw="background-image:...;"`) |
| Border     | `border borderColor radius shadow`               | `border` is `[w, '#color']`; a bare number is width                                                                                                                                                                                                |
| Type       | `size weight font lh ls ta`                      | Bare-number `lh` (up to 4) and `ls` read as **em** (`ls={-1}` collapses a headline); explicit units honored: `lh="150%"`, `ls="2px"`                                                                                                               |
| Misc       | `z`/`zIndex` `fit`                               | `fit` is object-fit                                                                                                                                                                                                                                |
| Responsive | `tablet={{...}} mobile={{...}}`                  | Breakpoint overrides with the same keys                                                                                                                                                                                                            |
| Escape     | `sx={{...}} props`                               | `sx` merges extra keys; `props` passes raw envelopes                                                                                                                                                                                               |

## 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:

  ```jsx theme={null}
  form({ name: 'contact', actions: ['collect-submissions'] }, [
    field('name', 'Name'),
    field('email', 'Email', { type: 'email' }),
    field('msg', 'Message', { textarea: true, rows: 5 }),
    formSubmit('Send message'),   // form() does NOT add a submit button by itself
  ])
  ```

  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](/ultra/media-and-fonts).

## Layout gotchas (each cost a real run)

<AccordionGroup>
  <Accordion title="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.
  </Accordion>

  <Accordion title="Row children stretch unless width-pinned">
    Row children get `flex: 1` unless width-pinned. Use `w: 'hug'` for
    justify-between clusters.
  </Accordion>

  <Accordion title="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'`.
  </Accordion>

  <Accordion title="The hidden burger must hide its widget, not just its button">
    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>`.
  </Accordion>

  <Accordion title="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`.
  </Accordion>

  <Accordion title="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.
  </Accordion>

  <Accordion title="Headings in flex columns overflow mobile">
    Headings inside flex columns need `w: '100%'` or they shrink to max-content
    and overflow on mobile.
  </Accordion>

  <Accordion title="Build width-general">
    Absolutes as `left: calc(50% +/- Npx)`, never fixed `left: Npx`; grids
    centered. Pages get viewed at 1200 through 2560.
  </Accordion>

  <Accordion title="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.
  </Accordion>

  <Accordion title="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 }`).
  </Accordion>

  <Accordion title="Nowrap text never wraps anywhere">
    Mono or `white-space: nowrap` text wraps nowhere; cap or wrap it for
    390px.
  </Accordion>

  <Accordion title="Image URLs">
    Absolute http(s) on the target site, single query param.
  </Accordion>

  <Accordion title="Marquees and bleed decorations">
    `raw="overflow:hidden;"` on the section, or the bleed widens the page.
  </Accordion>

  <Accordion title="Dark themes: verify contrast in screenshots">
    Body text at roughly `#b0b8c0` or lighter on near-black; judge contrast in
    screenshots, not by hex.
  </Accordion>
</AccordionGroup>

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

```bash theme={null}
eu-studio check --pages /,/about/ [--widths 1200,1920,390]    # the whole post-deploy gate, one call
eu-studio clicktest --form /contact/ --accordion /pricing/ --burger / --nav /=about
eu-studio doctor            # health + auto-heal; NAMES failing stylesheets
eu-studio carry-css --page <id>   # make pages independent of a flaky css file store
```

Full reference: [Verify](/ultra/verify) and [Measure](/ultra/measure).
