Skip to content

Controlled Tabs

A tabs component that tells its parent which tab is active through an onTabChange callback.

Builds on Tabs

Stage 4intro3 min read

Problem Statement

Build a tabs component. It takes a list of tabs, shows one tab's content at a time, and calls back to the parent with the index whenever the user picks a tab.

Functional Requirements

  1. Tab list: Render a button for each item in data, using its label.
  2. Active tab: The first tab is active on load. The active button gets data-active="true" for styling.
  3. Switch: Clicking a button makes that tab active and shows its content in the panel below.
  4. Callback: Call onTabChange(index) with the clicked tab's index. The demo parent logs it.

Technical Constraints

  • The active index lives in the Tabs component's own state. The parent is told about changes but does not set the active tab.
  • An empty data array or a missing onTabChange must not crash the component.

Component Structure

Props

{
  data: Array<{
    label: string,    // Text shown in tab button
    content: ReactNode // Content to display when tab is active
  }>,
  onTabChange: (index: number) => void  // Callback fired when tab changes
}

Usage Example

const tabsData = [
  {
    label: "Profile",
    content: <div>Profile Info Content</div>,
  },
  {
    label: "Dashboard",
    content: <div>Dashboard Content</div>,
  },
];

const onTabChangeHandler = (index) => {
  console.log("Current Tab Index:", index);
};

<Tabs data={tabsData} onTabChange={onTabChangeHandler} />;

State

currentTabIdx: number; // Tracks currently active tab index

Implementation Details

1. Tab Container

  • Uses flexbox for horizontal tab alignment
  • Implements gap spacing between tabs
  • Maintains visual connection with content panel

2. Tab Buttons

  • Interactive buttons with hover and active states
  • Uses data-active attribute for styling active tab
  • Implements onClick handler for tab switching and callback
  • Smooth transition effects

3. Content Panel

  • Displays content based on active tab index

Common Interview Questions

  1. How would you handle keyboard navigation? Put an onKeyDown on the tab list. Left and Right arrows move to the previous or next tab and wrap at the ends, Home and End jump to the first and last. Only the active tab has tabIndex={0}, the rest get -1, so Tab moves focus into the panel instead of through every tab.
  2. How would you make this component more accessible? Use role="tablist" on the container, role="tab" with aria-selected and aria-controls on each button, and role="tabpanel" with aria-labelledby on the content. Give each tab and panel a stable id, for example from useId.
  3. How would you implement lazy loading of tab content? Render only the active panel, as this component already does. For heavy panels, wrap them in React.lazy and Suspense, and keep a set of visited tabs if a panel should keep its state after you leave it.
  4. How would you handle tab overflow on small screens? Let the tab row scroll horizontally with overflow-x: auto, and scroll the active tab into view when it changes. Another option is to move tabs that do not fit into a "More" menu.
  5. How would you persist the active tab across page refreshes? Keep the index in the URL (?tab=2 or a hash) so it survives a refresh and can be shared. localStorage also works but is not shareable. Clamp a saved index that is out of range.
  6. How would you implement controlled vs uncontrolled versions of this component? Accept an optional activeIndex prop. When it is passed, render from it and only call onTabChange. When it is not, keep the index in local state as now, with a defaultIndex prop for the starting tab.

Potential Enhancements

  1. Add keyboard navigation (arrow keys)
  2. Implement ARIA attributes for accessibility
  3. Add animation for content transitions
  4. Add support for disabled tabs
  5. Implement vertical tab orientation option
  6. Add support for dynamic tab addition/removal
  7. Add controlled mode with external state management

Common Edge Cases to Handle

  1. Empty tab data array
  2. Very long tab labels
  3. Dynamic content height
  4. Missing onTabChange callback
  5. Multiple rapid tab changes

In this topic

Done with this topic?

How well do you know it?

PreviousTodo List
Practise 6 questions