Tabs

Tabs organize content into multiple sections that users can navigate between. The component supports various visual styles, orientations, and interactive behaviors while maintaining accessibility standards.

Basics

Pass items with a key, label, and content for each tab; the active tab's content renders below the tab list.

Loading demo…

import { Block, Tabs, Text } from '@plocks/ui'; const ITEMS = [ { key: 'overview', label: 'Overview', content: <Text>High-level summary and entry point.</Text> }, { key: 'details', label: 'Details', content: <Text>Deeper dive into metrics and configuration.</Text> }, { key: 'activity', label: 'Activity', content: <Text>Recent events, tasks, and notifications.</Text> } ]; export function Demo() { return ( <Block fullWidth> <Tabs items={ITEMS} /> </Block> ); }

Animated transitions

Enable animated and set animationDuration (in ms) to control how quickly the indicator slides between tabs.

Loading demo…

import { Block, Tabs, Text } from '@plocks/ui'; const ITEMS = [ { key: 'overview', label: 'Overview', content: <Text>High-level summary and entry point.</Text> }, { key: 'details', label: 'Details', content: <Text>Deeper dive into metrics and configuration.</Text> }, { key: 'settings', label: 'Settings', content: <Text>Manage workspace preferences.</Text> } ]; export function Demo() { return ( <Block fullWidth> <Tabs animated animationDuration={500} items={ITEMS} /> </Block> ); }

Disabled tabs

Show how to disable a tab while capturing interaction attempts through onDisabledTabPress for auditing or messaging.

Loading demo…

import { useState } from 'react'; import { Block, Tabs, Text } from '@plocks/ui'; const ITEMS = [ { key: 'overview', label: 'Overview', content: <Text>High-level snapshot of product activity and health.</Text> }, { key: 'analytics', label: 'Analytics', content: <Text>Dive into usage metrics, adoption trends, and retention.</Text> }, { key: 'billing', label: 'Billing', content: <Text>Billing is temporarily disabled while invoices reconcile.</Text>, disabled: true }, { key: 'settings', label: 'Settings', content: <Text>Manage workspace preferences and security controls.</Text> } ]; export function Demo() { const [lastAttempt, setLastAttempt] = useState<string | null>(null); return ( <Block fullWidth> <Tabs items={ITEMS} onDisabledTabPress={setLastAttempt} /> {lastAttempt && ( <Text variant="small" c="muted"> Pressed disabled tab: {lastAttempt} </Text> )} </Block> ); }

Controlled state

Demonstrates controlled tabs that surface the active label and rely on external state updates via onChange.

Loading demo…

import { useState } from 'react'; import { Block, Tabs, Text } from '@plocks/ui'; const ITEMS = [ { key: 'dashboard', label: '🏠 Dashboard', content: <Text>Monitor analytics across teams and products.</Text> }, { key: 'settings', label: '⚙️ Settings', content: <Text>Configure notifications, permissions, and integrations.</Text> }, { key: 'profile', label: '👤 Profile', content: <Text>Review contact details, roles, and security options.</Text> } ]; export function Demo() { const [activeTab, setActiveTab] = useState('dashboard'); const activeLabel = ITEMS.find((item) => item.key === activeTab)?.label ?? activeTab; return ( <Block fullWidth> <Tabs value={activeTab} onChange={setActiveTab} items={ITEMS} /> <Text variant="small" c="muted"> Active tab: {activeLabel} </Text> </Block> ); }

Location

Switch tab placement between start and end to position triggers above or below the associated content.

Loading demo…

import { Block, Tabs, Text } from '@plocks/ui'; const LOCATIONS = ['start', 'end'] as const; const ITEMS = [ { key: 'home', label: 'Home', content: <Text>Home content.</Text> }, { key: 'settings', label: 'Settings', content: <Text>Update your configuration.</Text> }, { key: 'profile', label: 'Profile', content: <Text>Profile information.</Text> } ]; export function Demo() { return ( <Block fullWidth> {LOCATIONS.map((location) => ( <Block key={location} fullWidth> <Text variant="small" c="secondary">{location}</Text> <Tabs location={location} items={ITEMS} /> </Block> ))} </Block> ); }
Demonstrates navigationOnly tabs that render the triggers separately while a custom container handles the content region.

Loading demo…

import { useMemo, useState } from 'react'; import { Block, Tabs, Text, useTheme } from '@plocks/ui'; const NAV_ITEMS = [ { key: 'home', label: 'Home' }, { key: 'products', label: 'Products' }, { key: 'about', label: 'About' }, { key: 'contact', label: 'Contact' } ] as const; const CONTENT_COPY: Record<typeof NAV_ITEMS[number]['key'], string> = { home: 'Welcome back! Navigation only mode keeps the tab strip separated from the view.', products: 'Highlight product cards, filters, or category grids below the navigation.', about: 'Share the company story, values, and milestones alongside persistent tabs.', contact: 'Surface support channels and locations while the navigation stays fixed.' }; export function Demo() { const theme = useTheme(); const [activeTab, setActiveTab] = useState<typeof NAV_ITEMS[number]['key']>('home'); const items = useMemo( () => NAV_ITEMS.map(({ key, label }) => ({ key, label, content: null })), [] ); return ( <Block fullWidth> <Tabs items={items} value={activeTab} onChange={(tabKey) => setActiveTab(tabKey as typeof NAV_ITEMS[number]['key'])} variant="chip" navigationOnly /> <Block bg={theme.backgrounds.surface} borderColor={theme.backgrounds.border} radius="lg" p="lg"> <Text>{CONTENT_COPY[activeTab]}</Text> </Block> </Block> ); }

Orientation

Compare horizontal and vertical tab lists to understand how orientation influences page layout.

Loading demo…

import { Block, Tabs, Text } from '@plocks/ui'; const ORIENTATIONS = ['horizontal', 'vertical'] as const; const ITEMS = [ { key: 'general', label: 'General', content: <Text>Broad overview content.</Text> }, { key: 'security', label: 'Security', content: <Text>Security controls and permissions.</Text> }, { key: 'notifications', label: 'Notifications', content: <Text>Configure alerts and digests.</Text> } ]; export function Demo() { return ( <Block fullWidth> {ORIENTATIONS.map((orientation) => ( <Block key={orientation} fullWidth> <Text variant="small" c="secondary">{orientation}</Text> <Tabs orientation={orientation} items={ITEMS} /> </Block> ))} </Block> ); }

Variants

Compare the line, chip, and folder visual variants to match tabs with different design aesthetics.

Loading demo…

import { Block, Tabs, Text } from '@plocks/ui'; const VARIANTS = ['line', 'chip', 'folder'] as const; const ITEMS = [ { key: 'overview', label: 'Overview', content: <Text>High-level summary and entry point.</Text> }, { key: 'details', label: 'Details', content: <Text>Deeper dive into metrics and configuration.</Text> }, { key: 'settings', label: 'Settings', content: <Text>Manage workspace preferences.</Text> } ]; export function Demo() { return ( <Block fullWidth> {VARIANTS.map((variant) => ( <Block key={variant} fullWidth> <Text variant="small" c="secondary">{variant}</Text> <Tabs variant={variant} items={ITEMS} /> </Block> ))} </Block> ); }
Scan to open this page on your phoneplocks.dev/ui/Tabs