# Feature tabs

> A switchable feature section that presents one capability at a time, with a heading, description and optional bullet points.

- ID: `cmp_feature_tabs_001`
- Slug: `feature-tabs-01`
- Version: `1.0.0` (current)
- Status: published
- Published: 2026-09-16
- Updated: 2026-09-17
- Available versions: `1.0.0`
- Kind: section
- Primary category: `features`
- Detail page: https://pagesugar.com/components/feature-tabs-01
- Preview: https://pagesugar.com/preview/feature-tabs-01

## Variants

| Variant | Label | Default | Artifact digest |
| --- | --- | --- | --- |
| `neutral` | Neutral | yes | `sha256-18ec61829023b8334d3d9be23df9b97b95562d770213afb31feb7d521d5e6280` |

## Runtime and compatibility

- Runtime: svelte
- Svelte: 5
- SvelteKit required: no (portable Svelte component)
- Tailwind CSS: 4
- SSR: supported
- Requires client-side JavaScript: yes
- Integration level: local-interaction
- Appearance modes: light
- Suggested directory: `src/lib/components/feature-tabs-01`

## Dependencies

- `bits-ui` `^2.0.0` (resolved at build time: `2.19.2`): Headless Tabs primitive providing tab/tablist/tabpanel roles, roving focus and arrow-key navigation.

## Services

No external services required.

## Usage

Supply feature tabs as data. The selected tab is local state; bind value or pass onValueChange to observe or control it. Requires the bits-ui package; no backend.

Required props: `features`, `title`

```svelte
<!-- Illustrative content: replace example claims, prices and links before publishing. -->
<script lang="ts">
	import FeatureTabs from '$lib/components/feature-tabs-01/FeatureTabs.svelte';
	import type { FeatureTab } from '$lib/components/feature-tabs-01/types';

	const features: FeatureTab[] = [
		{ value: 'plan', label: 'Plan', title: 'Plan work in one place', description: 'Collect tasks, owners and dates on a shared board.', points: ['Shared boards', 'Due dates'] },
		{ value: 'track', label: 'Track', title: 'See progress at a glance', description: 'Status updates roll up into a weekly summary.' }
	];

	let selected = $state('plan');
</script>

<FeatureTabs title="How it works" {features} bind:value={selected} />
```

Limitations:

- Install bits-ui (npm install bits-ui@^2) before using the component.
- Panel content is plain text; edit FeaturePanel.svelte to add media or rich content.
- Every feature value must be unique; it is used as the tab key.
- Tab switching needs JavaScript; before hydration only the initially selected panel is visible.

## Usage guide

### Adding the complete file set

Install the declared Bits UI dependency and copy all three files into `src/lib/components/feature-tabs-01/`, keeping these relative paths:

```text
FeatureTabs.svelte
parts/FeaturePanel.svelte
types.ts
```

Copying the entry component alone is not enough. The quick-start example shows how to import `FeatureTab` and bind the selected value. Example planning and reporting features describe a fictional product.

#### Extending a panel

For richer panel content, edit `parts/FeaturePanel.svelte` and extend `FeatureTab` in `types.ts` together. Keep each tab value unique and check that headings fit the surrounding page. For manual activation, add `activationMode="manual"` to `Tabs.Root`; verify keyboard behaviour after the change.

## Props

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `features` | `FeatureTab[]` | yes |  | Tabs in display order: { value, label, title, description, points? }. value must be unique. |
| `title` | `string` | yes |  | Section heading text; also labels the tab list. |
| `description` | `string` | no |  | Optional introductory paragraph under the heading. |
| `headingLevel` | 2 \| 3 \| 4 \| 5 | no | `2` | Level of the section heading; panel titles use the next level down. |
| `value` | `string` | no | `features[0].value` | Selected tab value. Bindable with bind:value. |
| `onValueChange` | `(value: string) => void` | no |  | Called with the new value whenever the user selects a different tab. Local UI callback only. |

## Customization

Change tabs through the features prop, edit the panel layout in parts/FeaturePanel.svelte, and edit Tailwind classes for colours and spacing. No colour tokens are declared.

- Content: add, remove or reorder entries in features; keep each value unique and stable.
- Panel layout: edit parts/FeaturePanel.svelte to add an image, link or different grid; extend FeatureTab in types.ts to match.
- Active tab style: change the data-\[state=active\]: border and text utilities on Tabs.Trigger.
- Colours: replace the zinc text, border and outline utilities together and keep text at 4.5:1 contrast.
- Activation: Bits UI activates tabs on focus by default; add activationMode="manual" to Tabs.Root if panels are expensive to render.
- Headings: set headingLevel to fit the page outline; panel titles use the next level.

No public CSS variables.

## Accessibility

- Implements the WAI-ARIA tabs pattern through Bits UI: tablist, tab and tabpanel roles with aria-selected, aria-controls and aria-labelledby.
- Arrow keys move between tabs (looping), Home and End jump to the first and last tab, and only the selected tab is in Tab order.
- The tab list is labelled by the section heading; the selected tab is indicated by text colour and an underline, not colour alone.
- Panels are focusable so keyboard users can reach panel content directly after the tab list; focus is shown with a visible outline.
- Tab and panel IDs, aria-controls, aria-labelledby and the roving tabindex are set explicitly from $props.id() and the selected value, so server-rendered markup is complete before hydration and multiple instances never collide.

Known limitations:

- On narrow screens tabs scroll horizontally; there is no visible scroll affordance beyond the clipped last tab.

## License

- Declared source: MIT
- Default license approval is pending. See https://pagesugar.com/docs/license.

## Artifacts

### Neutral (`neutral`) (default)

- Artifact digest: `sha256-18ec61829023b8334d3d9be23df9b97b95562d770213afb31feb7d521d5e6280`
- Entry: `FeatureTabs.svelte`
- Receipt: https://pagesugar.com/artifacts/cmp_feature_tabs_001/1.0.0/neutral/sha256-18ec61829023b8334d3d9be23df9b97b95562d770213afb31feb7d521d5e6280/manifest.json
- Bundle: https://pagesugar.com/artifacts/cmp_feature_tabs_001/1.0.0/neutral/sha256-18ec61829023b8334d3d9be23df9b97b95562d770213afb31feb7d521d5e6280/bundle.zip (5015 bytes, sha256 `0803b3e52603b520de2ecacfbcf1527d20230a821a8a80c0bbc353095c56e7f3`)

Files:

- `FeatureTabs.svelte` (entry, 2664 bytes): https://pagesugar.com/artifacts/cmp_feature_tabs_001/1.0.0/neutral/sha256-18ec61829023b8334d3d9be23df9b97b95562d770213afb31feb7d521d5e6280/source/FeatureTabs.svelte
- `parts/FeaturePanel.svelte` (component, 1226 bytes): https://pagesugar.com/artifacts/cmp_feature_tabs_001/1.0.0/neutral/sha256-18ec61829023b8334d3d9be23df9b97b95562d770213afb31feb7d521d5e6280/source/parts/FeaturePanel.svelte
- `types.ts` (types, 449 bytes): https://pagesugar.com/artifacts/cmp_feature_tabs_001/1.0.0/neutral/sha256-18ec61829023b8334d3d9be23df9b97b95562d770213afb31feb7d521d5e6280/source/types.ts
