# RealPlus Web Components — Usage Guide

> Generated from the component source. The structural facts (tags, attributes, slots) are
> extracted by the Custom Elements Manifest analyzer and cannot drift from what ships; the
> prose and examples are curated. See `manifest.json` for the machine-readable version.

## What this is

A library of presentational `rp-*` custom elements an LLM drops into report HTML. Load the
one bundle, write the tags, and the components render the polished, on-brand result. The
components are pure presentation — they never fetch data or own report logic; you supply
the data inline (as attributes/properties) or as nested children.

## Setup

Load the single bundle once per report — it registers every element and injects its own
styles. Then write component tags anywhere in the body.

```html
<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <script src="https://<cdn>/realplus-components.js"></script>
    <style>html,body{margin:0}</style> <!-- so the report fills the page -->
  </head>
  <body>
    <rp-report>
      <!-- your report here -->
    </rp-report>
  </body>
</html>
```

## Composition model

Two ways to configure a component, usable together:

- **Nested children (slots)** — *what pieces appear and where*. Place an `rp-media`, an
  `rp-fields`, an `rp-chip` inside an `rp-card`.
- **Attributes** — *how pieces are arranged*: density, columns, alignment, variant.

Every component degrades gracefully: missing/partial/unknown input falls back to a safe
default and never breaks the layout. Unknown attribute values are ignored.

## Authoring pattern: data first, then render

**For reports with repeated content — several listing cards, many table rows — define the
data first as plain JavaScript, then render it into `rp-*` markup in a separate step.** Do
not hand-write each repeated card or row. Put a `<script>` in the report that:

1. declares the data (an array of items, plus an optional block for content that repeats
   across items), then
2. maps each item through small render functions that return `rp-*` markup, and inserts the
   result into the page.

**The data shape is entirely up to you — there is no predefined schema.** Invent whatever
fields, names, and nesting the specific request needs (a listings report, a financials
summary, a comparison — each has its own natural shape). The components read the values you
hand them; they impose no field set of their own. The example below names its array
`LISTINGS` with listing fields only because that is what *that* report is about — yours can
look completely different.

This keeps content and layout separate: you edit the data to change *what* the report says,
and edit the render functions to change *how* it looks — without touching the data. Every
repeated item comes out structurally identical, and a missing field degrades gracefully
because the components handle blanks. Reserve hand-written tags for one-off chrome (the
masthead, the footer). **See the worked report under "Example reports" below — it is built
exactly this way.** For one-off content with no repetition, writing the tags directly is fine.

## Components

The **content & shell** set carries the report: the page, cards, fields, media, tables,
chips, and footer. The **furniture & layout** set is the surrounding prose, mastheads,
contact blocks, and spacing primitives — use them so a report is just the bundle + `rp-*`
tags with no hand-written CSS.

### Content & shell

#### `<rp-report>`

The report canvas: gray backdrop + a centered white US-Letter page. Wrap the whole report in it so plain markup (headings, prose, links) is styled on-brand and everything sits on one sheet.

| Attribute | Values / type | Default | Notes |
|-----------|---------------|---------|-------|
| `width` | `default` \| `wide` \| `full` | — |  |

#### `<rp-card>`

A self-contained block for one subject. Fill its header / media / body / footer regions with other components or content via `slot="…"`; unslotted children fall into the body.

| Attribute | Values / type | Default | Notes |
|-----------|---------------|---------|-------|
| `title` | `string` | — |  |
| `eyebrow` | `string` | — |  |
| `subtitle` | `string` | — |  |
| `density` | `comfortable` \| `compact` | `comfortable` |  |
| `variant` | `default` \| `elevated` \| `outline` | `default` |  |
| `media-position` | `leading` \| `trailing` | `leading` |  |
| `align` | `start` \| `center` | `start` |  |

**Slots:** `slot="header"` — title/status row (or set the `title` attribute); `slot="media"` — an rp-media block (or any media); collapses when empty; `slot="body"` — the main content (rp-fields, rp-table, prose); default for unslotted children; `slot="footer"` — supporting content / actions.

#### `<rp-fields>`

A labeled label/value list, optionally grouped. Supply rows inline via the `fields` property or as `rp-field` children.

| Attribute | Values / type | Default | Notes |
|-----------|---------------|---------|-------|
| `columns` | `1` \| `2` \| `3` | `1` |  |
| `label-position` | `above` \| `left` | `above` |  |
| `density` | `comfortable` \| `compact` | `comfortable` |  |
| `dividers` | `boolean` | `false` |  |
| `groups` | `boolean` | `true` |  |

**Slots:** default content — rp-field children whose rows merge after inline `fields` data.

**Properties (set via JS for inline data):** `.fields`.

**Child `<rp-field>`** — the value, if no value attribute

| Attribute | Notes |
|-----------|-------|
| `label` | Field label |
| `value` | Field value (or use text content) |
| `group` | Optional group heading |
| `empty` | Placeholder when value is empty (default —) |

#### `<rp-media>`

A media gallery for a subject — images, floor plans, video/tour links. Supply items via the `items` property or as `rp-media-item` children.

| Attribute | Values / type | Default | Notes |
|-----------|---------------|---------|-------|
| `variant` | `single-image` \| `small-slider` \| `big-slider` \| `image-grid` | `big-slider` |  |
| `grid-size` | `number` | `4` |  |
| `active-index` | `number` | `0` |  |

**Slots:** default content — rp-media-item children whose tiles merge after inline `items` data.

**Properties (set via JS for inline data):** `.items`.

**Child `<rp-media-item>`** — caption, if no caption attribute

| Attribute | Notes |
|-----------|-------|
| `src` | Image URL |
| `kind` | `photo` `floor-plan` `video` `tour` |
| `portrait` | Boolean — portrait orientation |
| `caption` | Caption text |
| `href` | Link URL (video/tour) |
| `thumbnail` | Thumbnail URL override |

#### `<rp-table>`

A scannable grid, one row per record. Declare `rp-column`s and `rp-row`/`rp-cell`s, or supply `columns`/`rows` inline. A cell may hold rich markup (e.g. an rp-chip).

| Attribute | Values / type | Default | Notes |
|-----------|---------------|---------|-------|
| `density` | `comfortable` \| `compact` | `comfortable` |  |
| `zebra` | `boolean` | `false` |  |
| `hide-header` | `boolean` | `false` |  |
| `groups` | `boolean` | `true` |  |
| `empty-text` | `string` | `No records to display.` |  |

**Slots:** default content — rp-column children (column defs) and rp-row children (one rp-cell per value).

**Properties (set via JS for inline data):** `.columns`, `.rows`.

**Child `<rp-column>`** — 

| Attribute | Notes |
|-----------|-------|
| `key` | Column key (matches rp-cell key) |
| `header` | Header label |
| `align` | `left` `center` `right` |
| `width` | CSS width or fr unit (e.g. `2fr`, `120px`) |

**Child `<rp-row>`** — contains rp-cell children

| Attribute | Notes |
|-----------|-------|
| `group` | Optional group heading for this row |

**Child `<rp-cell>`** — cell content — plain text OR rich markup like an rp-chip

| Attribute | Notes |
|-----------|-------|
| `key` | Column key |
| `value` | Cell text (or use child content) |

#### `<rp-chip>`

A small labeled pill for a short status or label. Drop it anywhere — a table cell, a card header, a fields value. The tone is author-chosen; the chip never infers meaning from its text.

| Attribute | Values / type | Default | Notes |
|-----------|---------------|---------|-------|
| `tone` | `neutral` \| `brand` \| `ai` \| `active` \| `pending` \| `sold` \| `expired` \| `future` \| `off-market` | `neutral` |  |
| `size` | `md` \| `sm` | `md` |  |
| `label` | `string | undefined` | — |  |

**Slots:** default content — the chip label (text or a `label` attribute).

#### `<rp-footer>`

A centered branded report footer. Drop it at the bottom of a report (inside rp-report it pins to the bottom of the page).

| Attribute | Values / type | Default | Notes |
|-----------|---------------|---------|-------|
| `variant` | `text` \| `logo` \| `both` | `text` |  |
| `logo` | `string` | `DEFAULT_LOGO` |  |
| `text` | `string` | `DEFAULT_TEXT` |  |

### Furniture & layout

#### `<rp-report-header>`

The masthead at the top of a report: a `title` on the left and a right-hand aside (agent / preparer / date — its light-DOM children) above a divider. `eyebrow` adds a small brand line.

| Attribute | Values / type | Default | Notes |
|-----------|---------------|---------|-------|
| `title` | `string` | — |  |
| `eyebrow` | `string` | — |  |

**Slots:** default content — the right-hand aside content (agent / preparer / date lines).

#### `<rp-contact>`

An agent / preparer contact block: a caption, name + role, and labeled contact methods. Use the `office` / `mobile` / `email` shorthands (email auto-links) or pass richer sets via the `methods` property.

| Attribute | Values / type | Default | Notes |
|-----------|---------------|---------|-------|
| `label` | `string` | `For further information, please contact` |  |
| `name` | `string` | — |  |
| `role` | `string` | — |  |
| `office` | `string` | — |  |
| `mobile` | `string` | — |  |
| `email` | `string` | — |  |

**Properties (set via JS for inline data):** `.methods`.

#### `<rp-heading>`

A heading at one of four type-scale levels (h1–h4). Generic — the LLM picks the `level`; it carries no domain meaning.

| Attribute | Values / type | Default | Notes |
|-----------|---------------|---------|-------|
| `level` | `1` \| `2` \| `3` \| `4` | `2` |  |

**Slots:** default content — the heading content (text or inline markup).

#### `<rp-text>`

A run of text at a chosen `tone` and `size`. A paragraph by default, or `inline` to flow within a line. Use it for prose, captions, and labels instead of bare `<p>`/`<span>`.

| Attribute | Values / type | Default | Notes |
|-----------|---------------|---------|-------|
| `tone` | `primary` \| `secondary` \| `tertiary` \| `brand` | `secondary` |  |
| `size` | `body` \| `small` \| `lead` \| `h4` | `body` |  |
| `weight` | `regular` \| `medium` \| `bold` | `regular` |  |
| `inline` | `boolean` | `false` |  |

**Slots:** default content — the text content (text or inline markup).

#### `<rp-eyebrow>`

A small uppercase label that captions the block beneath it (a section kicker, a brand line). `tone` is tertiary (muted) or brand (accent).

| Attribute | Values / type | Default | Notes |
|-----------|---------------|---------|-------|
| `tone` | `tertiary` \| `brand` | `tertiary` |  |

**Slots:** default content — the label text.

#### `<rp-stack>`

A vertical stack of its children with a consistent `gap`. A layout primitive — no chrome, just spacing.

| Attribute | Values / type | Default | Notes |
|-----------|---------------|---------|-------|
| `gap` | `xs` \| `sm` \| `md` \| `lg` | `sm` |  |
| `align` | `start` \| `center` \| `end` | `start` |  |

**Slots:** default content — the items to stack.

#### `<rp-split>`

Two regions side by side, wrapping to stacked on narrow widths. Children opt into a side with `slot="start"` / `slot="end"`. `ratio` skews the widths; `justify` pins the two to the edges.

| Attribute | Values / type | Default | Notes |
|-----------|---------------|---------|-------|
| `ratio` | `even` \| `start-wide` \| `end-wide` \| `start-narrow` \| `end-narrow` | `even` |  |
| `align` | `start` \| `center` \| `baseline` | `start` |  |
| `justify` | `boolean` | `false` |  |

**Slots:** `slot="start"` — the leading region (default for unmarked children); `slot="end"` — the trailing region.

#### `<rp-list>`

A list of rows, each a term with an optional right-aligned meta value (a transit stop and its distance). Rows come from the `items` property and/or `rp-list-item` children.

| Attribute | Values / type | Default | Notes |
|-----------|---------------|---------|-------|
| `caption` | `string` | — |  |

**Slots:** default content — rp-list-item children whose rows merge after inline `items` data.

**Properties (set via JS for inline data):** `.items`.

**Child `<rp-list-item>`** — the term, if no term attribute

| Attribute | Notes |
|-----------|-------|
| `term` | Row term (or use text content) |
| `meta` | Optional right-aligned meta value |

## Worked examples

These are the canonical compositions the gallery renders end to end.

### Single-listing detail card

```html
<rp-report>
  <rp-card variant="elevated" eyebrow="Flatiron District" title="245 Park Avenue South, 4B">
    <span slot="header"><rp-chip tone="active">Active</rp-chip></span>
    <rp-media slot="media" variant="big-slider">
      <rp-media-item src="https://…/1.jpg" caption="Living room"></rp-media-item>
      <rp-media-item src="https://…/2.jpg" kind="floor-plan"></rp-media-item>
    </rp-media>
    <rp-fields slot="body" columns="3">
      <rp-field label="Price" value="$3,850,000"></rp-field>
      <rp-field label="Beds" value="2"></rp-field>
      <rp-field label="Baths" value="2"></rp-field>
    </rp-fields>
  </rp-card>
  <rp-footer></rp-footer>
</rp-report>
```

### Multi-listing comparison / open-house grid

```html
<rp-report width="wide">
  <h2>Comparable listings</h2>
  <rp-table density="compact" zebra>
    <rp-column key="address" header="Address" width="2fr"></rp-column>
    <rp-column key="price" header="Price" align="right"></rp-column>
    <rp-column key="status" header="Status" align="center"></rp-column>
    <rp-row>
      <rp-cell key="address">245 Park Ave S, 4B</rp-cell>
      <rp-cell key="price">$3,850,000</rp-cell>
      <rp-cell key="status"><rp-chip tone="active">Active</rp-chip></rp-cell>
    </rp-row>
  </rp-table>
  <rp-footer variant="both"></rp-footer>
</rp-report>
```

### Report masthead + furniture (zero hand-written CSS)

The furniture & layout components carry the prose and chrome, so an LLM-authored report
needs no inline styles — just the bundle and `rp-*` tags.

```html
<rp-report>
  <rp-report-header title="Full Report w/Photo">
    <rp-text size="small" tone="tertiary">Jordan Avery</rp-text>
    <rp-text size="small" tone="tertiary">jordan.avery@example.com · M: (212) 555-0147</rp-text>
  </rp-report-header>
  <rp-card variant="outline" eyebrow="Chelsea, 10011" title="1. 154 Ninth Avenue">
    <rp-media slot="media" variant="single-image">
      <rp-media-item src="https://…/facade.jpg" caption="Façade"></rp-media-item>
    </rp-media>
    <rp-stack slot="header" gap="xs">
      <rp-chip tone="active">Active</rp-chip>
      <rp-text inline size="h4" weight="bold" tone="brand">$10,000,000</rp-text>
    </rp-stack>
    <rp-fields slot="body" columns="2" label-position="left" dividers>
      <rp-field label="Price" value="$10,000,000"></rp-field>
      <rp-field label="Units" value="3"></rp-field>
    </rp-fields>
    <rp-text slot="body" size="small">A fully gut-renovated mixed-use building in Chelsea…</rp-text>
  </rp-card>
  <rp-contact name="Jordan Avery" role="Licensed Associate" office="(212) 555-0100" email="jordan.avery@example.com"></rp-contact>
  <rp-footer></rp-footer>
</rp-report>
```

## Example reports

Complete, runnable reports built only from these components, served next to this guide and
the bundle. Fetch one to see a full report end to end, including the **data-first authoring
pattern** (a data array + a render step) described above.

- [`examples/detail.html`](./examples/detail.html) — a multi-listing detail report: one
  card per listing (header, photo + facts, description, rent roll, financials, transit,
  schools), all generated from a single `LISTINGS` data array.
