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

# Components

> The intrinsic tags and the kit helpers: containers, text, images, navigation, forms, and embeds.

## Intrinsics: the only tags

| Tag                               | Notes                                                                                               |
| --------------------------------- | --------------------------------------------------------------------------------------------------- |
| `box` `div` `col` `row` `section` | Flex containers. `row` forces direction row; `section` renders a real `<section>`                   |
| `h1` `h2` `h3` `h4` / `heading`   | Headings; text props apply; inline `<em>`, `<strong>`, `<br>` children are allowed                  |
| `text` `p`                        | Paragraph; `href` renders a **real anchor**, so this is how you make links and buttons-as-links     |
| `img`                             | `src` is a URL string (inline `alt` allowed) or an attachment id (alt comes from the media library) |
| `html`                            | Raw HTML/SVG/style/script carrier via the `raw` prop or children                                    |

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 bare `href="#"` is a placeholder: wire it or use a `#section`
  anchor (lint: `placeholder-link`).
* **Semantic containers** come from the `tag=` override on any container:
  `tag="header|footer|article|aside|a|button"`.

Special props on every intrinsic: `tw`, `raw`, `cls`, `gcls` (see
[Styling](/ultra/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

```jsx theme={null}
navBar({ logo, links: [['Work', '/work/'], ['About', '/about/']], ctas, accent, ink })
```

A complete header (desktop rail, dropdown mega-menus, mobile hamburger) as one
self-contained html widget. It is immune to the burger-steals-a-flex-slot bug by
construction (see [Troubleshooting](/ultra/troubleshooting)). Start here for
navs; hand-roll only when the design demands it, and then follow the
burger-wrapper rule religiously.

### Forms (Elementor Pro): the complete recipe

All four parts are 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 (the validator and the send-runner
  disagree). Submissions land in the `wp_e_submissions` table, 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

```jsx theme={null}
button(text, href, envelopeProps?)
```

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

## Next

<CardGroup cols={2}>
  <Card title="Media and fonts" icon="image" href="/ultra/media-and-fonts">
    The manifest contract for images and font files.
  </Card>

  <Card title="Troubleshooting" icon="wrench" href="/ultra/troubleshooting">
    The layout gotchas each of these rules prevents.
  </Card>
</CardGroup>
