React tabs UI component providing comprehensive, accessible, and customizable tabbed interfaces
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Interactive tab management with add/remove functionality and customizable controls. Editable tabs allow users to dynamically create and destroy tabs at runtime, providing a flexible interface for document editing, multi-view applications, and dynamic content management.
Comprehensive configuration for interactive tab management functionality.
/**
* Configuration for editable tabs functionality
* Enables dynamic tab creation and removal with customizable UI
*/
interface EditableConfig {
/**
* Callback function handling both add and remove operations
* @param type - Type of edit operation ('add' or 'remove')
* @param info - Context information about the operation
*/
onEdit: (
type: 'add' | 'remove',
info: {
key?: string;
event: React.MouseEvent | React.KeyboardEvent
}
) => void;
/** Whether to show the add button in the tab bar */
showAdd?: boolean;
/** Custom icon for remove buttons on closable tabs */
removeIcon?: React.ReactNode;
/** Custom icon for the add button */
addIcon?: React.ReactNode;
}
/**
* Edit operation context information
*/
interface EditInfo {
/** Tab key for remove operations (undefined for add operations) */
key?: string;
/** The mouse or keyboard event that triggered the operation */
event: React.MouseEvent | React.KeyboardEvent;
}Usage Examples:
import Tabs from "rc-tabs";
import { PlusOutlined, CloseOutlined } from "@ant-design/icons";
function EditableTabs() {
const [items, setItems] = useState([
{ key: '1', label: 'Tab 1', children: <div>Content 1</div>, closable: true },
{ key: '2', label: 'Tab 2', children: <div>Content 2</div>, closable: true },
]);
const [activeKey, setActiveKey] = useState('1');
const handleEdit = (type: 'add' | 'remove', info: { key?: string }) => {
if (type === 'add') {
const newKey = `tab-${Date.now()}`;
const newTab = {
key: newKey,
label: `New Tab ${items.length + 1}`,
children: <div>New tab content</div>,
closable: true,
};
setItems([...items, newTab]);
setActiveKey(newKey);
} else if (type === 'remove' && info.key) {
const newItems = items.filter(item => item.key !== info.key);
setItems(newItems);
// If removing active tab, switch to another tab
if (activeKey === info.key && newItems.length > 0) {
setActiveKey(newItems[0].key);
}
}
};
return (
<Tabs
activeKey={activeKey}
onChange={setActiveKey}
items={items}
editable={{
onEdit: handleEdit,
showAdd: true,
addIcon: <PlusOutlined />,
removeIcon: <CloseOutlined />,
}}
/>
);
}Dynamic tab creation with customizable add button and behavior.
/**
* Add tab configuration and behavior
*/
interface AddTabConfig {
/** Whether to display the add button in tab bar */
showAdd?: boolean;
/** Custom icon for the add button */
addIcon?: React.ReactNode;
/** Callback for add operations */
onAdd?: (event: React.MouseEvent | React.KeyboardEvent) => void;
}Add Tab Examples:
// Basic add functionality
function BasicAddTabs() {
const [tabs, setTabs] = useState(initialTabs);
const addTab = (type: 'add' | 'remove', info: { event: Event }) => {
if (type === 'add') {
const newTab = {
key: `new-${Date.now()}`,
label: 'New Tab',
children: <div>Fresh content</div>,
closable: true,
};
setTabs(prev => [...prev, newTab]);
}
};
return (
<Tabs
items={tabs}
editable={{
onEdit: addTab,
showAdd: true, // Shows + button in tab bar
}}
/>
);
}
// Custom add button with confirmation
function ConfirmAddTabs() {
const [tabs, setTabs] = useState(initialTabs);
const handleEdit = (type: 'add' | 'remove', info: EditInfo) => {
if (type === 'add') {
const name = prompt('Enter tab name:');
if (name) {
const newTab = {
key: `tab-${Date.now()}`,
label: name,
children: <div>{name} content</div>,
closable: true,
};
setTabs(prev => [...prev, newTab]);
}
}
};
return (
<Tabs
items={tabs}
editable={{
onEdit: handleEdit,
showAdd: true,
addIcon: <span>+ Add</span>, // Custom add button text
}}
/>
);
}Dynamic tab removal with confirmation and cleanup options.
/**
* Remove tab configuration and behavior
*/
interface RemoveTabConfig {
/** Custom icon for remove buttons */
removeIcon?: React.ReactNode;
/** Callback for remove operations */
onRemove?: (key: string, event: React.MouseEvent | React.KeyboardEvent) => void;
}Remove Tab Examples:
// Basic remove functionality
function BasicRemoveTabs() {
const [tabs, setTabs] = useState(initialTabs);
const [activeKey, setActiveKey] = useState('1');
const handleEdit = (type: 'add' | 'remove', info: EditInfo) => {
if (type === 'remove' && info.key) {
setTabs(prev => prev.filter(tab => tab.key !== info.key));
// Handle active tab removal
if (activeKey === info.key) {
const remainingTabs = tabs.filter(tab => tab.key !== info.key);
setActiveKey(remainingTabs.length > 0 ? remainingTabs[0].key : '');
}
}
};
return (
<Tabs
activeKey={activeKey}
onChange={setActiveKey}
items={tabs.map(tab => ({ ...tab, closable: true }))}
editable={{
onEdit: handleEdit,
removeIcon: <CloseOutlined style={{ fontSize: 12 }} />,
}}
/>
);
}
// Remove with confirmation
function ConfirmRemoveTabs() {
const [tabs, setTabs] = useState(initialTabs);
const handleEdit = (type: 'add' | 'remove', info: EditInfo) => {
if (type === 'remove' && info.key) {
const tabToRemove = tabs.find(tab => tab.key === info.key);
const confirmed = window.confirm(
`Are you sure you want to close "${tabToRemove?.label}"?`
);
if (confirmed) {
setTabs(prev => prev.filter(tab => tab.key !== info.key));
}
}
};
return (
<Tabs
items={tabs}
editable={{
onEdit: handleEdit,
removeIcon: <span>×</span>,
}}
/>
);
}Complex scenarios with validation, limits, and custom behaviors.
/**
* Advanced editable tab patterns and constraints
*/
interface AdvancedEditableConfig {
/** Maximum number of tabs allowed */
maxTabs?: number;
/** Minimum number of tabs required */
minTabs?: number;
/** Validation function for tab operations */
validateOperation?: (type: 'add' | 'remove', context: any) => boolean;
/** Custom behavior for different tab types */
tabTypeHandlers?: Record<string, EditHandler>;
}Advanced Examples:
// Limited tabs with validation
function LimitedEditableTabs() {
const [tabs, setTabs] = useState(initialTabs);
const maxTabs = 5;
const minTabs = 1;
const handleEdit = (type: 'add' | 'remove', info: EditInfo) => {
if (type === 'add') {
if (tabs.length >= maxTabs) {
alert(`Maximum ${maxTabs} tabs allowed`);
return;
}
const newTab = {
key: `tab-${Date.now()}`,
label: `Tab ${tabs.length + 1}`,
children: <div>Content {tabs.length + 1}</div>,
closable: tabs.length + 1 > minTabs, // Don't allow closing if at minimum
};
setTabs(prev => [...prev, newTab]);
} else if (type === 'remove' && info.key) {
if (tabs.length <= minTabs) {
alert(`Minimum ${minTabs} tab required`);
return;
}
setTabs(prev => prev.filter(tab => tab.key !== info.key));
}
};
return (
<Tabs
items={tabs}
editable={{
onEdit: handleEdit,
showAdd: tabs.length < maxTabs,
}}
/>
);
}
// Type-specific tab handling
function TypedEditableTabs() {
const [tabs, setTabs] = useState([
{ key: '1', type: 'document', label: 'Document 1', closable: true },
{ key: '2', type: 'settings', label: 'Settings', closable: false },
]);
const handleEdit = (type: 'add' | 'remove', info: EditInfo) => {
if (type === 'add') {
const tabType = 'document'; // Could be dynamic
const newTab = {
key: `${tabType}-${Date.now()}`,
type: tabType,
label: `New ${tabType}`,
children: createContentByType(tabType),
closable: tabType !== 'settings', // Settings tabs can't be closed
};
setTabs(prev => [...prev, newTab]);
} else if (type === 'remove' && info.key) {
const tab = tabs.find(t => t.key === info.key);
if (tab?.type === 'settings') {
alert('Settings tabs cannot be closed');
return;
}
// Save document before closing
if (tab?.type === 'document') {
const shouldSave = window.confirm('Save document before closing?');
if (shouldSave) {
saveDocument(tab.key);
}
}
setTabs(prev => prev.filter(tab => tab.key !== info.key));
}
};
return (
<Tabs
items={tabs}
editable={{
onEdit: handleEdit,
showAdd: true,
addIcon: <span>+ New Document</span>,
}}
/>
);
}Keyboard shortcuts and accessibility for editable tabs.
/**
* Keyboard interactions for editable tabs
* - Ctrl/Cmd + T: Add new tab (if showAdd is true)
* - Ctrl/Cmd + W: Close current tab (if closable)
* - Alt + Click: Alternative close action
*/
interface EditableKeyboardSupport {
addShortcut: 'Ctrl+T' | 'Cmd+T';
closeShortcut: 'Ctrl+W' | 'Cmd+W';
altCloseClick: 'Alt+Click';
}Keyboard Integration Example:
function KeyboardEditableTabs() {
const [tabs, setTabs] = useState(initialTabs);
const [activeKey, setActiveKey] = useState('1');
useEffect(() => {
const handleKeyboard = (e: KeyboardEvent) => {
if ((e.ctrlKey || e.metaKey) && e.key === 't') {
e.preventDefault();
addNewTab();
} else if ((e.ctrlKey || e.metaKey) && e.key === 'w') {
e.preventDefault();
closeCurrentTab();
}
};
document.addEventListener('keydown', handleKeyboard);
return () => document.removeEventListener('keydown', handleKeyboard);
}, [activeKey, tabs]);
const addNewTab = () => {
const newTab = {
key: `tab-${Date.now()}`,
label: 'New Tab',
children: <div>New content</div>,
closable: true,
};
setTabs(prev => [...prev, newTab]);
setActiveKey(newTab.key);
};
const closeCurrentTab = () => {
const currentTab = tabs.find(tab => tab.key === activeKey);
if (currentTab?.closable) {
handleEdit('remove', { key: activeKey, event: new KeyboardEvent('keydown') });
}
};
const handleEdit = (type: 'add' | 'remove', info: EditInfo) => {
// Standard edit handling...
};
return (
<Tabs
activeKey={activeKey}
onChange={setActiveKey}
items={tabs}
editable={{
onEdit: handleEdit,
showAdd: true,
}}
/>
);
}Install with Tessl CLI
npx tessl i tessl/npm-rc-tabs