nili/ui
All components

Tabs

Overview
19 exports6 variant axes32 props4 files
import { Tabs, TabsContent, TabsDivider, TabsList, TabsTrigger } from '@nili/ui';

This is the library’s own overview page, rendered live. Switch the language or the theme in the header and it follows.

Horizontal

The default. label names the tab set — without it a screen reader announces “tab list” and nothing else, which is useless on a page with three of them.

Your name, photo and bio.

import { Tabs, TabsList, TabsTrigger, TabsContent } from "@nili/ui"

export function Example() {
  return (
    <Tabs defaultValue="profile">
      <TabsList label={t('settings.label')}>
        <TabsTrigger value="profile">{t('tab.profile')}</TabsTrigger>
        <TabsTrigger value="security">{t('tab.security')}</TabsTrigger>
        <TabsTrigger value="alerts">{t('tab.alerts')}</TabsTrigger>
      </TabsList>

      <TabsContent value="profile">Your name, photo and bio.</TabsContent>
      <TabsContent value="security">Password and two-factor.</TabsContent>
      <TabsContent value="alerts">What we email you about.</TabsContent>
    </Tabs>
  )
}

Icons and badges

startIcon and endIcon are logical, so they lead in both directions. badge takes any node — a bare count or a full Badge.

Your name, photo and bio.

import { Tabs, TabsList, TabsTrigger, TabsContent, Badge, Icon } from "@nili/ui"
import { RiUser3Line, RiNotification3Line } from "@remixicon/react"

export function Example() {
  return (
    <Tabs defaultValue="profile">
      <TabsList label="Account settings">
        <TabsTrigger value="profile" startIcon={<Icon icon={RiUser3Line} />}>
          {t('tab.profile')}
        </TabsTrigger>
        <TabsTrigger
          value="alerts"
          startIcon={<Icon icon={RiNotification3Line} />}
          badge={<Badge size="small" color="red">3</Badge>}
        >
          {t('tab.alerts')}
        </TabsTrigger>
      </TabsList>
      …
    </Tabs>
  )
}

Stretch

stretch makes the tabs share the width instead of hugging their labels — right for a segmented control inside a card or a mobile sheet.

The last seven days.

import { Tabs, TabsList, TabsTrigger, TabsContent } from "@nili/ui"

export function Example() {
  return (
    <Tabs defaultValue="week">
      <TabsList stretch label={t('range.label')}>
        <TabsTrigger value="day">{t('range.day')}</TabsTrigger>
        <TabsTrigger value="week">{t('range.week')}</TabsTrigger>
        <TabsTrigger value="month">{t('range.month')}</TabsTrigger>
      </TabsList>
      …
    </Tabs>
  )
}

Vertical

appearance applies to vertical tabs only — the horizontal menu has no style axis in the design. TabsDivider rules off a group of tabs.

card

Your name, photo and bio.

list

Your name, photo and bio.

import { Tabs, TabsList, TabsTrigger, TabsContent, TabsDivider } from "@nili/ui"

export function Example() {
  return (
    <Tabs defaultValue="profile" orientation="vertical" appearance="card">
      <TabsList label="Settings">
        <TabsTrigger value="profile">{t('tab.profile')}</TabsTrigger>
        <TabsTrigger value="security">{t('tab.security')}</TabsTrigger>
        <TabsDivider />
        <TabsTrigger value="usage">Usage</TabsTrigger>
      </TabsList>
      …
    </Tabs>
  )
}

Activation mode and keepMounted

automatic selects a tab as focus reaches it — the ARIA default, right when panels are cheap. Use manual when a tab loads data: arrowing past four tabs should not fire four requests. keepMounted preserves scroll position and form state in a hidden panel.

Arrow between the tabs — with `manual`, nothing changes until you press Enter or Space.

import { Tabs, TabsList, TabsTrigger, TabsContent } from "@nili/ui"

export function Example() {
  return (
    <Tabs defaultValue="overview" activationMode="manual">
      <TabsList label={t('report.label')}>
        <TabsTrigger value="overview">{t('report.overview')}</TabsTrigger>
        <TabsTrigger value="chart">{t('report.chart')}</TabsTrigger>
      </TabsList>

      <TabsContent value="overview">Cheap to render.</TabsContent>

      {/* expensive to rebuild — keep it mounted */}
      <TabsContent value="chart" keepMounted>
        <RevenueChart />
      </TabsContent>
    </Tabs>
  )
}