# Fallback content and slots

> What to write inside a component's tag so the page works before the component loads, when it cannot load, and where the component shows your content.

> Esta página ainda não tem tradução para o português; este é o texto em inglês. (This page is not translated yet: the English text follows.)

What you write **inside** a component's tag is plain HTML of your own page (the light DOM). Taggor uses it
two ways:

- **Fallback:** what shows while the component has not loaded, or if it never loads.
- **Slots:** pieces the component shows inside itself, in the place it declared (`slot="title"`).

## What the visitor sees

| Situation                                                                                                                        | What appears                                                                                                        |
| -------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| Before the component loads (`:not(:defined)`)                                                                                    | All the HTML inside the tag, as on any page                                                                         |
| The component loaded                                                                                                             | The component; children with `slot="name"` appear inside it, in the slot's place; children without `slot` disappear |
| The Taggor script is blocked (ad blocker, the page's CSP, the CDN is down) or the contract version is not one the runtime mounts | The element is never activated: all the HTML inside stays visible and working                                       |
| The component threw an error                                                                                                     | The children without `slot` (the fallback); `slot` content is hidden; the rest of the page is untouched             |
| A browser without JavaScript                                                                                                     | All the HTML inside the tag, and what is in `<noscript>`                                                            |

## The pattern

```html
<style>
  /* Before the component loads: the size it will take, so the page does not jump. */
  finance-simulator:not(:defined) {
    display: block;
    min-height: 420px;
  }
</style>

<finance-simulator rate="1.4">
  <span slot="title">Simulate now</span>
  <a href="https://example.com/simulator">Simulate on our site</a>
  <noscript>Turn on JavaScript to simulate here.</noscript>
</finance-simulator>
```

- **The fallback is the child without `slot`** (the link). It has to work on its own: a link to the same
  function on your site, a phone number, an e-mail. It is visible before the load, when Taggor does not
  arrive and when the component breaks, and it goes away when the component shows.
- **`slot="title"`** works only for the slot names the component declares in `slots` (see the component's
  documentation). The content appears inside the component, and is hidden if the component breaks.
- **`:not(:defined)`** holds only until the tag is activated. Use it to give the element its final size (the
  component's `size`: `minHeight`, `maxWidth`), so swapping the fallback for the component does not push the
  rest of the page.
- **`<noscript>`** is only for what makes sense **without JavaScript**, such as a notice. With JavaScript on,
  the browser never shows `<noscript>`, not even when the Taggor script is blocked.

## Mistakes to avoid

- **Never put the only fallback inside `<noscript>`.** The most common reason a component does not load is
  JavaScript on and the script blocked; then `<noscript>` is hidden and the visitor sees nothing.
- **Never hide the fallback with `:not(:defined)` and no time limit** (`visibility: hidden`, `display: none`, zero
  opacity). If the Taggor script never arrives, the tag is never activated and the content stays hidden for
  good. A hold that ends by itself, as in [Load Taggor early](https://taggor.dev/docs/load-early.md), is safe.
- **If the component declares the `default` slot,** children without `slot` appear inside it, and the
  fallback would no longer go away. Components that expect a fallback do not declare `default`; one that does
  says so in its own documentation.
- **Slot content stays yours.** The component shows your element itself, not a copy: its events
  (`addEventListener`), a field's state and your CSS keep working. The component never reads or rewrites that
  HTML.
- **Search and accessibility:** the fallback is document HTML, which search engines read without running
  JavaScript. Once the component shows, the fallback leaves the screen and the accessibility tree, so screen
  readers do not read both.

## For whoever writes the component (the customer's agent)

- Declare each slot in `slots` of `defineTaggorComponent` and render a native `<slot name="…">` where the
  content should appear. Never rebuild the light DOM content (`innerHTML`, `textContent`, cloning nodes): a
  copy loses the page's events and, with `innerHTML`, opens XSS (Taggor's lint blocks the HTML sinks).
- `useSlot("title").hasContent` says whether the page filled the slot and follows changes, so you can show a
  default title only when the slot is empty. For plain default text, the content inside the `<slot
name="title">Default title</slot>` itself also works (the browser's native fallback).
- Do not declare `default` if the page should be able to write a fallback.

This example declares one slot (imports left out):

```ts title="a component with one slot (imports left out)"
export default defineTaggorComponent({
  name: "fallback-card",
  tag: "fallback-card",
  title: "Fallback card",
  description: "A call to action whose title the page can replace; plain HTML stands in for it.",
  props: { headline: prop.string({ maxLength: 60, default: "Simule seu financiamento" }) },
  slots: { title: { description: "Replaces the default headline." } },
  styles: [
    ":host { display: block; min-height: 120px; padding: 8px; box-sizing: border-box }",
    "h3 { margin: 0 0 8px }",
  ],
  render: ({ headline }) => {
    const title = useSlot("title");
    return h(
      "section",
      // Exposed for the browser test: what `useSlot` reported on the last render.
      { "data-title-slot": title.hasContent ? "filled" : "empty" },
      h("h3", null, title.hasContent ? h("slot", { name: "title" }) : headline),
      h("button", { type: "button" }, "Simular"),
    );
  },
});
```

## Tested

The page above is the fixture `packages/component/fixtures/fallback-card.html`, run in a browser by
`packages/runtime/e2e/slots.e2e.ts` for each row of the table, including a blocked script and a component that
throws. The browsers the matrix covers, and what an old browser gets, are in the browser policy of the
repository (`docs/testing/compatibility.md`): the loader does nothing and the page stays as written.

## Show them early

To hold a region of the page until its components are defined, with a time limit that shows this fallback if
Taggor does not arrive, see [Load Taggor early](https://taggor.dev/docs/load-early.md).
