# content-tabs

Tabs and accordions — single, multi, or toggle selection, paired by name
or position.


```html
<content-tabs class="underline">
  <content-tabs-header is-open>Overview</content-tabs-header>
  <content-tabs-header>Details</content-tabs-header>
  <content-tabs-header>Settings</content-tabs-header>
  <content-tabs-body is-open><p>Overview copy goes here.</p></content-tabs-body>
  <content-tabs-body><p>Details copy goes here.</p></content-tabs-body>
  <content-tabs-body><p>Settings copy goes here.</p></content-tabs-body>
</content-tabs>
```


## Features

- **Single / multi / toggle** Radio-like tabs, an accordion, or re-click
  to close
- **Pair by name or position** Match headers to bodies with `tab-name`,
  or by index when unnamed
- **Isolated nesting** Nested `<content-tabs>` groups never cross-wire
- **Bindable state** `.provision` is `{ tabType, openTabs, activeTab }` —
  read the active tab from Quark with `prop("provision")`
- **Included looks** `.underline` and `.file-tabs` styles ship built in

## Installation


`@excom/content-tabs` v0.1.5

```bash
pnpm add @excom/content-tabs
```

```bash
npm install @excom/content-tabs
```

```bash
yarn add @excom/content-tabs
```

### Import

```ts
import "@excom/content-tabs";
```



## Usage

Put `<content-tabs-header>` and `<content-tabs-body>` elements inside
`<content-tabs>`. A header pairs with the body sharing its `tab-name`,
or by position when both are unnamed.

```html
<content-tabs class="underline">
  <content-tabs-header is-open>Overview</content-tabs-header>
  <content-tabs-header>Details</content-tabs-header>
  <content-tabs-body is-open><p>Overview copy.</p></content-tabs-body>
  <content-tabs-body><p>Details copy.</p></content-tabs-body>
</content-tabs>
```

The open state is a fact, not just an event: `.provision` holds
`{ tabType, openTabs, activeTab }`, where a tab is its header's `tab-name`
(or its index when unnamed) and `activeTab` is the first open one. Quark
reads it on the group and binds it anywhere below:

```quark
content-tabs {
  $tab: prop("provision").activeTab;
  h2 { content: $tab; }
}
```

### API Reference


#### Attributes

| Name | Surface | Type | Default | Values | Description |
| --- | --- | --- | --- | --- | --- |
| `tab-type` | option | `string` | `"single"` | `"single"` \| `"multi"` \| `"toggle"` | Selection mode. `single` opens one header at a time (radio-like); `multi` allows any number open at once (accordion); `toggle` is like `single`, but re-clicking the open header closes it. |

#### Provision

| Name | Type | Description |
| --- | --- | --- |
| `provision` | `ContentTabsProvision` (`{ tabType: string \| null; openTabs: Array<string \| number>; activeTab: string \| number \| null; }`) | Open state: `{ tabType, openTabs, activeTab }`. A tab is its header's `tab-name`, or its index among this group's own headers when unnamed; `activeTab` is the first open one (`null` when none). Set after mount and after every header change (batched with the body sync). Not reflected as an attribute. |

#### Recognized Elements

| Relationship | Selector | Required | Description |
| --- | --- | --- | --- |
| `content-tabs-header` | child | yes | Clickable tab headers. |
| `content-tabs-body` | child | yes | Panels shown / hidden to match their header. |

#### Listens for

| Name | Type | Description |
| --- | --- | --- |
| `content-tabs-header-opened` | `ContentTabsHeaderOpenedEvent` (`CustomEvent & { type: "content-tabs-header-opened"; detail: void; bubbles: true; cancelable: true; composed: true }`) | Closes sibling headers when `tab-type` is `single` (default) or `toggle`. |

#### CSS Custom Properties

| Name | Syntax | Default | Description |
| --- | --- | --- | --- |
| `--content-tabs-border-color` | `<color>` | `var(--v-muted-border-color, currentColor)` | Border color for `.file-tabs` headers / bodies. |
| `--content-tabs-border-width` | `<length>` | `var(--v-border-width, 2px)` | Border width for `.file-tabs` headers / bodies. |
| `--content-tabs-color-active` | `<color>` | `var(--v-color-primary, inherit)` | Text color for the active header in `.underline`. |
| `--content-tabs-header-padding` | `<length>{1,4}` | `10px 15px` | Padding for each header. |
| `--content-tabs-body-padding` | `<length>{1,4}` | `var(--v-spacing, 15px)` | Padding for each body in `.file-tabs`. |
| `--content-tabs-active-indicator-height` | `<length>` | `2px` | Height of the `.file-tabs` active-tab accent bar. |

#### CSS Classes

| Name | Description |
| --- | --- |
| `.file-tabs` | File-explorer look — headers render as bordered folder tabs joined to the active body. |
| `.underline` | Paper / underline look — the active header gets a bottom accent border instead of a bordered card. |

#### CSS Aliases

| Alias | Kind | Matches | Description |
| --- | --- | --- | --- |
| `:--content-tabs` | element | `content-tabs`, `.tag-content-tabs` |  |
| `:--content-tab--is-open` | state | `[is-open]`, `[aria-selected="true"]` |  |



### Examples

#### File tabs

`.file-tabs` renders each header like a folder tab, joined to its body.


```html
<content-tabs class="file-tabs">
  <content-tabs-header is-open>index.ts</content-tabs-header>
  <content-tabs-header>utils.ts</content-tabs-header>
  <content-tabs-body is-open><pre>export const value = 1;</pre></content-tabs-body>
  <content-tabs-body><pre>export const double = (n) => n * 2;</pre></content-tabs-body>
</content-tabs>
```


#### Multi-select accordion

`tab-type="multi"` lets any number of headers stay open — clicking one does not close the others. Also displayed are the tab headers as buttons, if using Valence.css.

Useful for building your own UI toggle systems.


```html
<content-tabs tab-type="multi">
  <content-tabs-header is-open role="button">Shipping</content-tabs-header>
  <content-tabs-header role="button">Billing</content-tabs-header>
  <content-tabs-body is-open><p>Ships in 3-5 business days.</p></content-tabs-body>
  <content-tabs-body><p>Charged on shipment, not on order.</p></content-tabs-body>
</content-tabs>
```


#### Pair by tab-name

Give a header and body matching `tab-name` values to pair them regardless of their order in the DOM.


```html
<content-tabs class="underline">
  <content-tabs-body tab-name="b"><p>Body B — paired by name, not position.</p></content-tabs-body>
  <content-tabs-body tab-name="a" is-open><p>Body A — paired by name, not position.</p></content-tabs-body>
  <content-tabs-header tab-name="a" is-open>A</content-tabs-header>
  <content-tabs-header tab-name="b">B</content-tabs-header>
</content-tabs>
```

## Release notes

### 0.1.1 (2026-09-23)

- Ship only dist (and declared extras) in the npm tarball; drop build logs, tests and sources
