npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@xsolla/xui-tabs

v0.110.0

Published

A cross-platform React tabs component for organizing content into multiple panels that users can switch between. Implements WAI-ARIA tablist pattern for accessibility.

Readme

Tabs

A cross-platform React tabs component for organizing content into multiple panels that users can switch between. Implements WAI-ARIA tablist pattern for accessibility.

Variants:

  • Line (default): Traditional underlined tabs with bottom border indicator
  • Segmented: Button-group style segmented control with sliding active indicator

Installation

npm install @xsolla/xui-tabs
# or
yarn add @xsolla/xui-tabs

Demo

Basic Tabs

import * as React from 'react';
import { Tabs, TabPanel } from '@xsolla/xui-tabs';

export default function BasicTabs() {
  const [activeTab, setActiveTab] = React.useState('tab1');

  const tabs = [
    { id: 'tab1', label: 'Overview' },
    { id: 'tab2', label: 'Features' },
    { id: 'tab3', label: 'Pricing' },
  ];

  return (
    <div>
      <Tabs
        id="basic-tabs"
        tabs={tabs}
        activeTabId={activeTab}
        onChange={setActiveTab}
      />
      <TabPanel id="tab1" tabsId="basic-tabs" hidden={activeTab !== 'tab1'}>
        <p>Overview content goes here.</p>
      </TabPanel>
      <TabPanel id="tab2" tabsId="basic-tabs" hidden={activeTab !== 'tab2'}>
        <p>Features content goes here.</p>
      </TabPanel>
      <TabPanel id="tab3" tabsId="basic-tabs" hidden={activeTab !== 'tab3'}>
        <p>Pricing content goes here.</p>
      </TabPanel>
    </div>
  );
}

Tabs with Icons

import * as React from 'react';
import { Tabs } from '@xsolla/xui-tabs';
import { Home, User, Settings } from '@xsolla/xui-icons';

export default function TabsWithIcons() {
  const [activeTab, setActiveTab] = React.useState('home');

  const tabs = [
    { id: 'home', label: 'Home', icon: <Home size={16} /> },
    { id: 'profile', label: 'Profile', icon: <User size={16} /> },
    { id: 'settings', label: 'Settings', icon: <Settings size={16} /> },
  ];

  return (
    <Tabs
      tabs={tabs}
      activeTabId={activeTab}
      onChange={setActiveTab}
    />
  );
}

Tabs with Counter

import * as React from 'react';
import { Tabs } from '@xsolla/xui-tabs';

export default function TabsWithCounter() {
  const [activeTab, setActiveTab] = React.useState('inbox');

  const tabs = [
    { id: 'inbox', label: 'Inbox', counter: 12 },
    { id: 'sent', label: 'Sent', counter: 5 },
    { id: 'drafts', label: 'Drafts', counter: 3 },
    { id: 'spam', label: 'Spam', badge: true },
  ];

  return (
    <Tabs
      tabs={tabs}
      activeTabId={activeTab}
      onChange={setActiveTab}
    />
  );
}

Tabs Sizes

import * as React from 'react';
import { Tabs } from '@xsolla/xui-tabs';

export default function TabsSizes() {
  const tabs = [
    { id: 'a', label: 'Tab A' },
    { id: 'b', label: 'Tab B' },
    { id: 'c', label: 'Tab C' },
  ];

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 24 }}>
      <Tabs tabs={tabs} activeTabId="a" size="sm" />
      <Tabs tabs={tabs} activeTabId="a" size="md" />
      <Tabs tabs={tabs} activeTabId="a" size="lg" />
      <Tabs tabs={tabs} activeTabId="a" size="xl" />
    </div>
  );
}

Segmented Variant

A button-group style segmented control with a sliding active indicator animation.

import * as React from 'react';
import { Tabs } from '@xsolla/xui-tabs';

export default function SegmentedTabs() {
  const [activeTab, setActiveTab] = React.useState('daily');

  const tabs = [
    { id: 'daily', label: 'Daily' },
    { id: 'weekly', label: 'Weekly' },
    { id: 'monthly', label: 'Monthly' },
  ];

  return (
    <Tabs
      tabs={tabs}
      activeTabId={activeTab}
      onChange={setActiveTab}
      variant="segmented"
    />
  );
}

Stretched Tabs

Use the stretched prop to make tabs fill the entire container width.

import * as React from 'react';
import { Tabs } from '@xsolla/xui-tabs';

export default function StretchedTabs() {
  const [activeTab, setActiveTab] = React.useState('tab1');

  const tabs = [
    { id: 'tab1', label: 'First' },
    { id: 'tab2', label: 'Second' },
    { id: 'tab3', label: 'Third' },
  ];

  return (
    <div style={{ width: '100%' }}>
      {/* Line variant stretched */}
      <Tabs
        tabs={tabs}
        activeTabId={activeTab}
        onChange={setActiveTab}
        stretched
      />

      {/* Segmented variant stretched */}
      <Tabs
        tabs={tabs}
        activeTabId={activeTab}
        onChange={setActiveTab}
        variant="segmented"
        stretched
      />
    </div>
  );
}

Anatomy

Import the components and compose them:

import { Tabs, TabPanel } from '@xsolla/xui-tabs';

// Tab list
<Tabs
  id="my-tabs"                // Unique ID for ARIA
  tabs={tabItems}             // Array of tab definitions
  activeTabId={activeId}      // Currently active tab
  onChange={handleChange}     // Tab selection handler
  size="md"                   // Size variant
  variant="line"              // Visual variant: "line" | "segmented"
  stretched={false}           // Fill container width
  alignLeft={true}            // Alignment (line variant only)
  activateOnFocus={true}      // Auto-activate on focus
/>

// Tab panels
<TabPanel
  id="tab-id"                 // Matches tab.id
  tabsId="my-tabs"            // Parent Tabs id
  hidden={!isActive}          // Visibility control
>
  Panel content
</TabPanel>

Examples

Disabled Tab

import * as React from 'react';
import { Tabs } from '@xsolla/xui-tabs';

export default function DisabledTab() {
  const [activeTab, setActiveTab] = React.useState('active');

  const tabs = [
    { id: 'active', label: 'Active Tab' },
    { id: 'disabled', label: 'Disabled Tab', disabled: true },
    { id: 'another', label: 'Another Tab' },
  ];

  return (
    <Tabs
      tabs={tabs}
      activeTabId={activeTab}
      onChange={setActiveTab}
    />
  );
}

Manual Activation

import * as React from 'react';
import { Tabs } from '@xsolla/xui-tabs';

export default function ManualActivation() {
  const [activeTab, setActiveTab] = React.useState('tab1');

  const tabs = [
    { id: 'tab1', label: 'First' },
    { id: 'tab2', label: 'Second' },
    { id: 'tab3', label: 'Third' },
  ];

  return (
    <div>
      <p>Use Arrow keys to navigate, Enter/Space to activate</p>
      <Tabs
        tabs={tabs}
        activeTabId={activeTab}
        onChange={setActiveTab}
        activateOnFocus={false}
      />
    </div>
  );
}

Full Example with Content

import * as React from 'react';
import { Tabs, TabPanel } from '@xsolla/xui-tabs';
import { Settings } from '@xsolla/xui-icons';
import { LayoutDashboard, BarChart3, FileText } from '@xsolla/xui-icons-base';

export default function FullTabsExample() {
  const [activeTab, setActiveTab] = React.useState('dashboard');

  const tabs = [
    { id: 'dashboard', label: 'Dashboard', icon: <LayoutDashboard size={16} /> },
    { id: 'analytics', label: 'Analytics', icon: <BarChart3 size={16} />, counter: 5 },
    { id: 'reports', label: 'Reports', icon: <FileText size={16} /> },
    { id: 'settings', label: 'Settings', icon: <Settings size={16} /> },
  ];

  return (
    <div style={{ width: '100%' }}>
      <Tabs
        id="main-tabs"
        tabs={tabs}
        activeTabId={activeTab}
        onChange={setActiveTab}
        size="md"
      />

      <div style={{ padding: 24, border: '1px solid #eee', borderTop: 'none' }}>
        <TabPanel id="dashboard" tabsId="main-tabs" hidden={activeTab !== 'dashboard'}>
          <h3>Dashboard</h3>
          <p>Welcome to your dashboard overview.</p>
        </TabPanel>

        <TabPanel id="analytics" tabsId="main-tabs" hidden={activeTab !== 'analytics'}>
          <h3>Analytics</h3>
          <p>View your analytics and metrics here.</p>
        </TabPanel>

        <TabPanel id="reports" tabsId="main-tabs" hidden={activeTab !== 'reports'}>
          <h3>Reports</h3>
          <p>Generate and view reports.</p>
        </TabPanel>

        <TabPanel id="settings" tabsId="main-tabs" hidden={activeTab !== 'settings'}>
          <h3>Settings</h3>
          <p>Configure your preferences.</p>
        </TabPanel>
      </div>
    </div>
  );
}

API Reference

Tabs

The tab list component.

Tabs Props:

| Prop | Type | Default | Description | | :--- | :--- | :------ | :---------- | | tabs | TabItemType[] | - | Required. Array of tab definitions. | | activeTabId | string | - | Currently active tab ID. | | onChange | (id: string) => void | - | Callback when tab selection changes. | | size | "xl" \| "lg" \| "md" \| "sm" | "md" | Size of the tabs. | | variant | "line" \| "segmented" | "line" | Visual variant. Line shows underlined tabs; segmented shows button-group style. | | stretched | boolean | false | Whether to stretch tabs to fill container width. | | alignLeft | boolean | true | Left vs center alignment (line variant only). | | activateOnFocus | boolean | true | Auto-activate tab on focus. | | id | string | - | Unique ID for ARIA association. | | aria-label | string | - | Accessible label for tab list. | | aria-labelledby | string | - | ID of labeling element. | | testID | string | - | Test identifier. |

TabItemType:

interface TabItemType {
  id: string;               // Unique tab identifier
  label: string;            // Display label
  icon?: ReactNode;         // Optional icon
  counter?: string | number; // Optional counter badge
  badge?: boolean | string | number; // Optional badge
  disabled?: boolean;       // Whether tab is disabled
  "aria-label"?: string;    // Custom accessible label
}

TabPanel

Container for tab content.

TabPanel Props:

| Prop | Type | Default | Description | | :--- | :--- | :------ | :---------- | | id | string | - | Required. Matches corresponding tab.id. | | tabsId | string | - | Required. Parent Tabs component id. | | hidden | boolean | - | Whether panel is hidden. | | children | ReactNode | - | Panel content. | | aria-label | string | - | Accessible label. | | testID | string | - | Test identifier. |

Keyboard Navigation

| Key | Action | | :-- | :----- | | Arrow Right/Down | Move to next enabled tab | | Arrow Left/Up | Move to previous enabled tab | | Home | Jump to first enabled tab | | End | Jump to last enabled tab | | Enter/Space | Activate tab (when activateOnFocus is false) |

Theming

Tabs uses the design system theme for colors:

Line Variant

// Colors accessed via theme
theme.colors.content.primary     // Active tab text
theme.colors.content.tertiary    // Inactive tab text
theme.colors.border.primary      // Active tab indicator (bottom border)
theme.colors.border.secondary    // Container bottom border
theme.colors.overlay.mono        // Hover background

Segmented Variant

// Colors accessed via theme
theme.colors.control.segmented.bg         // Container background
theme.colors.control.segmented.bgHover    // Tab hover background
theme.colors.control.segmented.bgActive   // Active tab indicator background
theme.colors.control.segmented.textActive // Active tab text color
theme.colors.control.text.primary         // Inactive tab text
theme.colors.control.text.disable         // Disabled tab text

Accessibility

  • Implements WAI-ARIA tablist pattern
  • Uses role="tablist", role="tab", role="tabpanel"
  • aria-selected indicates active tab
  • aria-controls links tabs to panels
  • aria-labelledby links panels to tabs
  • Full keyboard navigation support
  • Focus management follows ARIA best practices
  • Segmented variant sliding indicator is marked with aria-hidden

Platform Support

Both variants support web and React Native platforms:

  • Web: Segmented variant includes smooth sliding animation for the active indicator
  • React Native: Falls back to static background without animation