# Shadcn tabs in React: design clear views without losing context

> Build shadcn tabs in React with clear labels, keyboard access, and Radix-based behavior.
> By Dave · 2026-10-12
> Source: https://otf-kit.dev/blog/shadcn-tabs-howto

A tab row can make a crowded React screen easier to scan, but only when every tab represents a sibling view of the same subject. Use tabs to switch between closely related panels, not as a substitute for your app’s main navigation or a way to hide information people need at once. This guide walks through that decision, a shadcn tabs implementation, and the checks that keep the interaction usable.

## Decide whether the content belongs in tabs

Tabs work when a person stays in one context and chooses which slice of it to inspect. A project page might offer Overview, Activity, and Members. Each panel describes the same project from a different angle; changing tabs does not mean the person has moved to a different area of the product.

That relationship is the key test. If a label takes the user to a different product destination, use links or a sidebar. If the choices filter one collection, a filter control may be clearer. If readers need to compare every section at once, use headings and a page outline instead of hiding most of the information in inactive panels.

A useful question is: would someone expect the other choices to remain available as views of this same record? If yes, tabs may fit. If no, the control may be navigation, filtering, or a segmented choice with a different meaning. This distinction helps you avoid a row that looks compact but forces people to hunt for content that should be visible.

Keep the panels comparable in purpose and scale. A short profile summary beside a large analytics workspace can make the tab set feel arbitrary. Move unrelated tools to a page-level navigation area, and keep the tab labels focused on the content in their own panels.

![Dex and Nova compare a tabbed content view with a separate navigation menu.](https://cdn.otf-kit.dev/blog/shadcn-tabs-howto/inbody1-tabs-versus-navigation-20261012a.png)

## Choose labels and a predictable default

Write labels that name the information inside each panel. “Overview,” “Activity,” and “Settings” tell users what to expect. “More,” “Other,” or a series of icons alone make the result harder to scan, especially when a person returns to the screen after time away.

Keep labels short enough to fit the available width. On a narrow screen, test the real labels with the longest one selected. Do not shrink the text until it is difficult to read. If the set no longer fits comfortably, consider whether the tabs still describe a single context, or whether a different navigation layout would serve the page better.

Choose a default panel that answers the most common first question. The first tab should not be selected only because it was easiest to implement. If the page represents a project, an Overview panel may orient a new visitor; if the page is a report, the most useful summary may be the better default. Make that decision from the user's task, not from a preference for one visual state.

The selected state should be clear without relying on color alone. Check the tab text, underline or other indicator, and focus ring in both light and dark themes. Make sure inactive tabs remain recognizable as available choices and that the active label remains visible when the panel below changes.

## Build the tabs with matching values

The OTF React SDK exposes `Tabs`, `TabsList`, `TabsTrigger`, and `TabsContent` from `@otfdashkit/ui`. Give each trigger and its panel the same stable value, and set a default that corresponds to one of those values:

```tsx
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@otfdashkit/ui";

export function ProjectViews() {
  return (
    <Tabs defaultValue="overview">
      <TabsList aria-label="Project views">
        <TabsTrigger value="overview">Overview</TabsTrigger>
        <TabsTrigger value="activity">Activity</TabsTrigger>
        <TabsTrigger value="members">Members</TabsTrigger>
      </TabsList>

      <TabsContent value="overview">
        <p>Project status and the next useful action.</p>
      </TabsContent>
      <TabsContent value="activity">
        <p>Recent changes made to this project.</p>
      </TabsContent>
      <TabsContent value="members">
        <p>People who can access this project.</p>
      </TabsContent>
    </Tabs>
  );
}
```

The values connect each control to its panel; the visible label is for the person using the interface. Keep values stable and descriptive rather than deriving them from translated labels. That way, changing display copy does not silently break the connection.

Use an uncontrolled default when the component can own the selected tab. If another part of the screen needs to coordinate the selection, manage it in React state with `value` and `onValueChange`. Keep one source of truth: avoid maintaining a separate local selection that can drift from the tabs component.

The OTF component is built on Radix UI Tabs. That gives you a styled building block while keeping the familiar tabs composition. The [Tabs component page](/components/tabs) shows the installation command and a working example; install the free SDK with `bun add @otfdashkit/ui` before importing the component.

## Make keyboard use part of the design

![A user tests keyboard focus moving between tabs and their panels.](https://cdn.otf-kit.dev/blog/shadcn-tabs-howto/inbody2-keyboard-focus-panels-20261012a.png)

A tab set is not complete because the pointer interaction works. Test it with a keyboard: Tab should move into the active tab set, the arrow keys should move through the triggers, and Home and End should reach the first and last trigger. In the default automatic activation mode, moving to a trigger also activates its panel. The active control needs a visible focus treatment throughout that movement.

Give the tab list an accessible name when the surrounding page does not make its purpose obvious. Use ordinary text labels whenever they fit. If an icon is necessary, provide a readable name instead of leaving users to infer its meaning from shape or color.

Keep useful content in the panel associated with its trigger. Do not put an unrelated action in the tab list, and do not use a tab as a button for an operation such as deleting or saving. People should be able to predict that choosing a different tab changes the panel, not the underlying record.

Forms need an extra pass. When a person changes tabs after entering information, decide whether the form state should remain. Test that behavior instead of assuming a hidden panel will preserve every child component's state. Keep save and error feedback close to the fields that caused it, and make sure a validation message is still discoverable if the person switches away and returns.

## Keep the experience usable on small screens

Check the tab row at the widths your product supports. Long labels can wrap into uneven buttons or push the panel below the fold. Prefer clear wording and an appropriate layout over tiny type. If a horizontal row must scroll, make that movement obvious and keep the selected tab from being clipped after a change.

Also test panel content separately from the tabs. Tables, charts, and forms may need their own responsive behavior. A tab set can be accessible while the content inside one panel still overflows the viewport. Use the narrowest supported viewport and check every panel, not only the default one.

Before shipping, verify the default selection, the trigger-to-panel values, keyboard movement, focus visibility, label clarity, and mobile layout. Check with realistic content; placeholder labels rarely expose wrapping or alignment problems.

For a related component decision, see [how to keep icon choices consistent across a React interface](/blog/shadcn-icons-react-consistent-ui). The same principle applies: make the control's meaning legible before you polish its appearance.

## Start with the component, then choose your app foundation

If you already have a React screen, open the [OTF Tabs example](/components/tabs), install the SDK, and adapt the example to your own content. If you are building a full product rather than adding one screen, you can preview the [SaaS Dashboard kit](/templates/saas-dashboard) before deciding whether its broader app structure fits. The live demo lets you inspect the starting point, then the kit page explains the next step.

Tabs are a small component, but they shape how people explore a product. When the options are true sibling views, labels are direct, the default is useful, and the keyboard path works, the interface can stay compact without making information feel hidden.

## Sources

- [Radix UI Tabs documentation](https://www.radix-ui.com/primitives/docs/components/tabs)
- [OTF Tabs component page](/components/tabs)
