preline
Version:
Preline UI is an open-source set of prebuilt UI components based on the utility-first Tailwind CSS framework.
326 lines (237 loc) • 11.7 kB
Markdown
# Tabs
Tabs make it easy to switch between different views.
[](https://www.npmjs.com/package/@preline/tabs) [](https://opensource.org/licenses/MIT) [](https://preline.co/plugins/tabs.html)
## Contents
- [Overview](#overview)
- [Installation](#installation)
- [Basic usage](#basic-usage)
- [Accessibility notes](#accessibility-notes)
- [Configuration Options](#configuration-options)
- [JavaScript API](#javascript-api)
- [Events](#events)
- [Common Patterns](#common-patterns)
- [License](#license)
## Overview
The Tabs component allows users to switch between different content sections within the same context. It supports keyboard navigation, programmatic control, and customizable event types for activation.
**Key Features:**
- Multiple tab panels in a single group
- Keyboard navigation support (Arrow keys, Home, End, Enter)
- Customizable activation events (click, hover)
- Programmatic control via JavaScript API
- Event system for tab change tracking
- Accessibility attributes (ARIA) built-in
## Installation
To get started, install Tabs plugin via npm, else you can skip this step if you are already using Preline UI as a package.
```bash
npm i /tabs
```
### CSS
Use [``](https://tailwindcss.com/docs/functions-and-directives#source-directive) to register the plugin's JavaScript path for Tailwind CSS scanning, then [`@import`](https://tailwindcss.com/docs/functions-and-directives#import-directive) the plugin's CSS files into your Tailwind CSS file.
```css
"tailwindcss";
/* @preline/tabs */
"../node_modules/@preline/tabs/*.js";
"./node_modules/@preline/tabs/variants.css";
"./node_modules/@preline/tabs/theme.css";
```
### JavaScript
Include the JavaScript that powers the interactive elements near the end of your `</body>` tag:
```html
<script src="./node_modules/@preline/tabs/index.js"></script>
```
### Manual Initialization
Use the `non-auto` entry if you need manual initialization. In this mode, automatic initialization on page load is not included, so the component should be initialized explicitly.
```html
<script type="module">
import HSTabs from "@preline/tabs/non-auto.mjs";
new HSTabs(document.querySelector("#tabs"));
</script>
```
### Via Bundler
When using a bundler (Vite, webpack, etc.), import the plugin directly as an ES module.
`/tabs` is the auto-init entry: it scans the DOM and initializes matching elements automatically.
```js
import "@preline/tabs";
```
`/tabs/non-auto` is the manual entry: use it when you want explicit control over when initialization happens, either via `autoInit()` or by creating a specific instance yourself.
```js
import HSTabs from "@preline/tabs/non-auto";
HSTabs.autoInit();
// Or initialize a specific element manually
const el = document.querySelector("#tabs");
if (el) new HSTabs(el);
```
### TypeScript
This package ships with TypeScript type definitions. No additional `/` package is needed.
## Basic usage
The following example demonstrates the minimal HTML structure required for a tabs component. This is a base template without custom styling - you can apply your own CSS classes and styles as needed. The example shows three tabs with corresponding content panels, with the first tab active by default.
```html
<nav class="flex gap-x-2" aria-label="Tabs" role="tablist">
<button type="button" class="active" id="hs-unstyled-tabs-item-first" aria-selected="true" data-hs-tab="#hs-unstyled-tabs-first" aria-controls="hs-unstyled-tabs-first" role="tab">
Tab 1
</button>
<button type="button" id="hs-unstyled-tabs-item-second" aria-selected="false" data-hs-tab="#hs-unstyled-tabs-second" aria-controls="hs-unstyled-tabs-second" role="tab">
Tab 2
</button>
<button type="button" id="hs-unstyled-tabs-item-third" aria-selected="false" data-hs-tab="#hs-unstyled-tabs-third" aria-controls="hs-unstyled-tabs-third" role="tab">
Tab 3
</button>
</nav>
<div class="mt-3">
<div id="hs-unstyled-tabs-first" role="tabpanel" aria-labelledby="hs-unstyled-tabs-item-first">
This is the <em>first</em> item's tab body.
</div>
<div id="hs-unstyled-tabs-second" class="hidden" role="tabpanel" aria-labelledby="hs-unstyled-tabs-item-second">
This is the <em>second</em> item's tab body.
</div>
<div id="hs-unstyled-tabs-third" class="hidden" role="tabpanel" aria-labelledby="hs-unstyled-tabs-item-third">
This is the <em>third</em> item's tab body.
</div>
</div>
```
**Structure Requirements:**
- `role="tablist"`: Required on the container element (nav, div, etc.)
- `role="tab"`: Required on each tab button
- `role="tabpanel"`: Required on each content panel
- `data-hs-tab`: Required on each tab button, must be a valid CSS selector pointing to the corresponding tabpanel
- Unique `id` attributes for each tab button and its corresponding panel
- Proper ARIA attributes (`aria-selected`, `aria-controls`, `aria-labelledby`)
**Initial State:**
- To have a tab active by default, add the `active` class to the tab button and set `aria-selected="true"`
- For inactive tabs, add the `hidden` class to the tabpanel and set `aria-selected="false"`
## Accessibility notes
### Keyboard interactions
| Command | Description |
| --- | --- |
| ArrowLeft / ArrowRight | Selects the previous/next non-disabled tab. |
| ArrowUp / ArrowDown (in vertical mode) | Selects the previous/next non-disabled tab. |
| Home / End | Selects the first/last non-disabled tab. |
| Enter | Activates the selected tab. |
## Configuration Options
### Data Attributes
Data attributes are added directly to HTML elements to configure tab behavior.
| Attribute | Target Element | Type | Default | Description |
| --- | --- | --- | --- | --- |
| `data-hs-tabs` | Tablist container | object (JSON) | - | Contains configuration options for customizing tab behavior and appearance. |
| `:eventType` | Inside `data-hs-tabs` | `"click"` \| `"hover"` | `"click"` | Set `hover` to activate tabs on mouseover, or `click` for manual clicks (the default). |
| `data-hs-tab` | Tab button (trigger) | string (CSS selector) | - | Activate a tab by specifying the selector of the target tabpanel. This must be a valid CSS selector pointing to the tabpanel element. |
| `data-hs-tab-select` | Tablist container | string (ID) | - | You can pass a select ID there. Each option of this select must have a value equal to the tab ID (e.g., `<option value="#hs-tab-to-select-first">Tab 1</option>`). When you select this value, the corresponding tab will be opened. |
**Example:**
```html
<nav data-hs-tabs='{"eventType": "hover"}' aria-label="Tabs" role="tablist">
<button data-hs-tab="#hs-tab-first" role="tab">Tab 1</button>
</nav>
```
### Tailwind Modifiers
| Name | Description |
| --- | --- |
| `hs-tab-active:*` | A modifier that allows you to set Tailwind classes when the tab is active. Can be applied to both the toggle button and the content panel. |
## JavaScript API
The `HSTabs` object is available in the global `window` object after the plugin is loaded.
### Instance Methods
These methods are called on a tabs instance.
| Method | Parameters | Return Type | Description |
| --- | --- | --- | --- |
| `destroy()` | None | `void` | Destroys the tabs instance, removes all generated markup, classes, and event listeners. Use when removing tabs from DOM. |
### Static Methods
These methods are called directly on the `HSTabs` class.
| Method | Parameters | Return Type | Description |
| --- | --- | --- | --- |
| `HSTabs.getInstance(target, isInstance)` | `target`: `HTMLElement \| string` (CSS selector)<br>`isInstance`: `boolean` (optional) | `HSTabs \| { id: string \| number, element: HSTabs } \| null` | Returns the tabs instance associated with the target. If `isInstance` is `true`, returns collection item object `{ id, element }` where `element` is the `HSTabs` instance. If `isInstance` is `false` or omitted, returns the `HSTabs` instance directly. Returns `null` if tabs instance is not found. |
| `HSTabs.open(target)` | `target`: `HTMLElement` | `void` | Opens the tab identified by target. The target should be a tab button element. Static method to programmatically switch tabs. |
### Usage Examples
**Example 1: Destroying tabs instance**
```javascript
const instance = HSTabs.getInstance('#hs-tabs', true);
if (instance) {
const { element } = instance;
const destroyBtn = document.querySelector('#hs-destroy-btn');
destroyBtn.addEventListener('click', () => {
element.destroy();
});
}
```
**Example 2: Opening tab programmatically (static method)**
```javascript
const openBtn = document.querySelector('#hs-open-btn');
openBtn.addEventListener('click', () => {
const tabButton = document.querySelector('#hs-tab-first');
if (tabButton) {
HSTabs.open(tabButton);
}
});
```
**Example 3: Getting instance and using methods (recommended pattern)**
```javascript
// Get the tabs instance
const instance = HSTabs.getInstance('#hs-tabs', true);
if (instance) {
const { element } = instance;
// Access instance properties
console.log('Current tab:', element.current);
console.log('Current content:', element.currentContent);
// Clean up when removing from DOM
function removeTabs() {
element.destroy();
}
}
```
## Events
Tabs instances emit events that can be listened to for lifecycle hooks and custom behavior.
| Event Name | When Fired | Callback Parameter | Description |
| --- | --- | --- | --- |
| `on:change` | When any tab is changed | `{ el: HTMLElement, prev: string, current: string }` | Fires when a tab is switched. Returns an object with:<br>- `el`: The toggle button element that was clicked<br>- `prev`: Previous tab ID<br>- `current`: Current tab ID |
### Event Usage Example
```javascript
// Get tabs instance
const instance = HSTabs.getInstance('#hs-tabs', true);
if (instance) {
const { element } = instance;
// Listen to change event
element.on('change', ({ el, prev, current }) => {
console.log('Tab changed:', {
clickedButton: el,
previousTab: prev,
currentTab: current
});
// Perform actions after tab changes
// e.g., load content, update UI, track analytics
});
}
```
## Common Patterns
### Pattern 1: Hover Activation
Activate tabs on hover instead of click.
```html
<nav data-hs-tabs='{"eventType": "hover"}' aria-label="Tabs" role="tablist">
<button data-hs-tab="#hs-tab-first" role="tab">Tab 1</button>
<button data-hs-tab="#hs-tab-second" role="tab">Tab 2</button>
</nav>
```
### Pattern 2: Programmatic Control
Control tabs from external buttons.
```html
<nav id="hs-tabs-first" aria-label="Tabs" role="tablist">
<button data-hs-tab="#hs-tab-content-first" role="tab">Tab 1</button>
<button data-hs-tab="#hs-tab-content-second" role="tab">Tab 2</button>
</nav>
<button id="hs-open-tab-first">Open Tab 1</button>
<button id="hs-open-tab-second">Open Tab 2</button>
<script>
document.querySelector('#hs-open-tab-first').addEventListener('click', () => {
const tabButton = document.querySelector('[data-hs-tab="#hs-tab-content-first"]');
if (tabButton) {
HSTabs.open(tabButton);
}
});
document.querySelector('#hs-open-tab-second').addEventListener('click', () => {
const tabButton = document.querySelector('[data-hs-tab="#hs-tab-content-second"]');
if (tabButton) {
HSTabs.open(tabButton);
}
});
</script>
```
## License
Copyright (c) 2026 Preline Labs.
Licensed under the [MIT License](https://opensource.org/licenses/MIT).