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

# Add a tab

> Add a tab of your own to a host page, beside Tappify's.

A tab is a full-width surface that sits in a host page's tab strip, next to Tappify's own
tabs, with your vendor tile on the label.

```bash theme={null}
tappify extension add tab --name Ratings --page analytics
```

## The manifest entry

```json theme={null}
{
  "contributes": {
    "tabs": [
      {
        "id": "ratings",
        "title": "Ratings",
        "entry": "src/tabs/ratings.tsx",
        "page": "analytics"
      }
    ]
  }
}
```

The command writes this entry. You can hand-edit `tappify.extension.json` instead — the
`$schema` line gives your editor completion and validation, and
[`tappify extension doctor`](/extensions/test/doctor-checks) checks the result.

A tab validates on `overview`, `analytics` and `deployments`. The host draws it on Analytics
and Deployments today; Home accepts the contribution and does not render it yet — see
[Pages and slots](/extensions/reference/pages-and-slots). `mobile: false` hides the tab on
phone layouts.

## The component

`TapTabProps` is empty — a tab reads everything it needs from the bridge.

```tsx theme={null}
import {
  TapPageHeader,
  TapTable,
  useTapFilters,
  useTapQuery,
  type TapTabProps,
} from "@tappify/extension-sdk";
import "@tappify/extension-sdk/styles.css";

export default function Ratings(_props: TapTabProps) {
  const filters = useTapFilters();
  const reviews = useTapQuery({ kind: "reviews", range: filters.range });

  return (
    <div className="tap-stack">
      <TapPageHeader
        title="Ratings"
        description="Every review Tappify holds for the range the page is showing."
      />
      <TapTable
        columns={[
          { key: "submittedAt", label: "Submitted" },
          { key: "country", label: "Country" },
          { key: "rating", label: "Rating", align: "end" },
        ]}
        rows={reviews.data?.reviews ?? []}
        rowKey={(row) => row.id}
        empty="No reviews in this range."
      />
    </div>
  );
}
```

A `reviews` query needs `reviews:read`, and the result carries `reviews`, `averageRating`
and `total`. See [Scopes](/extensions/reference/scopes).

## What the host renders

The tab strip is Tappify's. Your label carries the vendor tile; selecting it mounts your
component full width with `tap.ui.size` set to `page`. The four boundary states from
[Add a widget](/extensions/build/add-a-widget) apply here too — a tab that fails to load
shows the failure card in the tab body, and the rest of the page is untouched.

A tab always renders at the page's own width, so use container queries against your mount
rather than viewport media queries. The viewport tells you nothing about how wide you are.
