Skip to main content
TabbedChips (Alpha)
@coinbase/cds-web@8.30.1
A chip component commonly used in filter context to refine a date source
Alpha componentAlpha components are stable and safe to use. They allow us to provide new and powerful features quickly, without forcing breaking changes. Components will exit the alpha status when their deprecated counterpart is removed in the next major version.
Import
import { TabbedChips } from '@coinbase/cds-web/alpha/tabbed-chips/TabbedChips'
SourceView source codeStorybookView StorybookFigmaView Figma (internal only)
Related components
View as Markdown

Basic usage

Loading...
Live Code
function ExampleDefault() {
  const tabs = [
    { id: 'all', label: 'All' },
    { id: 'swap', label: 'Swap' },
    { id: 'collect', label: 'Collect' },
    { id: 'bridge', label: 'Bridge' },
  ];
  const [activeTab, setActiveTab] = useState(tabs[0]);
  return <TabbedChips activeTab={activeTab} onChange={setActiveTab} tabs={tabs} />;
}

Compact

function ExampleCompactNoStart() {
const tabs = [
{ id: 'all', label: 'All' },
{ id: 'swap', label: 'Swap' },
{ id: 'collect', label: 'Collect' },
{ id: 'bridge', label: 'Bridge' },
];
const [activeTab, setActiveTab] = useState(tabs[0]);
return <TabbedChips activeTab={activeTab} compact onChange={setActiveTab} tabs={tabs} />;
}

Many tabs (paddles)

Paddles & overflow

Paddles appear automatically when the tab list overflows.

Loading...
Live Code
function ExampleWithPaddles() {
  const tabs = Array.from({ length: 12 }).map((_, i) => ({
    id: `tab_${i + 1}`,
    label: `Tab ${i + 1}`,
  }));
  const [activeTab, setActiveTab] = useState(tabs[0]);
  return <TabbedChips activeTab={activeTab} onChange={setActiveTab} tabs={tabs} />;
}

With autoScrollOffset

Auto-scroll offset

The autoScrollOffset prop controls the X position offset when auto-scrolling to the active tab. This prevents the active tab from being covered by the paddle on the left side. Try clicking tabs near the edges to see the difference.

Loading...
Live Code
function ExampleAutoScrollOffset() {
  const tabs = Array.from({ length: 25 }).map((_, i) => ({
    id: `tab_${i + 1}`,
    label: `Tab ${i + 1}`,
  }));
  const [activeTab, setActiveTab] = useState(tabs[0]);
  return (
    <VStack gap={2}>
      <Text as="p" font="body">
        Default offset (50px)
      </Text>
      <TabbedChips activeTab={activeTab} onChange={setActiveTab} tabs={tabs} />
      <Text as="p" font="body">
        Custom offset (100px)
      </Text>
      <TabbedChips
        activeTab={activeTab}
        onChange={setActiveTab}
        tabs={tabs}
        autoScrollOffset={100}
      />
    </VStack>
  );
}

With custom sized paddles

Paddle styling

You can adjust the size of the paddles via styles.paddle.

Loading...
Live Code
function ExampleCustomPaddles() {
  const tabs = Array.from({ length: 10 }).map((_, i) => ({
    id: `t_${i + 1}`,
    label: `Item ${i + 1}`,
  }));
  const [activeTab, setActiveTab] = useState(tabs[0]);
  return (
    <TabbedChips
      activeTab={activeTab}
      onChange={setActiveTab}
      tabs={tabs}
      styles={{ paddle: { transform: 'scale(0.8)' } }}
    />
  );
}

Long text tabs

Loading...
Live Code
function ExampleLongText() {
  const tabs = [
    { id: 'a', label: 'Very long tab label that can wrap on small widths' },
    { id: 'b', label: 'Another extra long label to test overflow' },
    { id: 'c', label: 'Short' },
  ];
  const [activeTab, setActiveTab] = useState(tabs[0]);
  return <TabbedChips activeTab={activeTab} onChange={setActiveTab} tabs={tabs} />;
}

Disabled tab

Loading...
Live Code
function ExampleDisabled() {
  const tabs = [
    { id: 'first', label: 'First' },
    { id: 'second', label: 'Second', disabled: true },
    { id: 'third', label: 'Third' },
  ];
  const [activeTab, setActiveTab] = useState(tabs[0]);
  return <TabbedChips activeTab={activeTab} onChange={setActiveTab} tabs={tabs} />;
}

With start media

Media sizing

For start media, use circular images sized 24×24 for regular chips and 16×16 for compact chips.

Loading...
Live Code
function ExampleWithStart() {
  const icon = { height: 24, width: 24, shape: 'circle', source: assets.eth.imageUrl };
  const compactIcon = { height: 16, width: 16, shape: 'circle', source: assets.eth.imageUrl };
  const tabs = [
    { id: 'all', label: 'All', start: <RemoteImage {...icon} /> },
    { id: 'swap', label: 'Swap', start: <RemoteImage {...icon} /> },
    { id: 'collect', label: 'Collect', start: <RemoteImage {...icon} /> },
    { id: 'bridge', label: 'Bridge', start: <RemoteImage {...icon} /> },
  ];
  const compactTabs = tabs.map((tab) => ({ ...tab, start: <RemoteImage {...compactIcon} /> }));
  const [activeTab, setActiveTab] = useState(tabs[0]);
  return (
    <VStack gap={2}>
      <TabbedChips activeTab={activeTab} onChange={setActiveTab} tabs={tabs} />
      <TabbedChips compact activeTab={activeTab} onChange={setActiveTab} tabs={compactTabs} />
    </VStack>
  );
}

Custom TabComponent

Custom Tab behavior

When providing a custom TabComponent, use useTabsContext() and call updateActiveTab(id) to update selection state. Reflect the active state (e.g., end icon state) based on activeTab?.id === id.

Loading...
Live Code
function CustomTab({ id, label, ...props }: TabbedChipProps) {
  const { activeTab, updateActiveTab } = useTabsContext();
  const isActive = activeTab?.id === id;
  return (
    <MediaChip
      end={<Icon size="s" active={isActive} name="star" />}
      onClick={() => updateActiveTab(id)}
      {...props}
    >
      {label}
    </MediaChip>
  );
}

const tabs = [
  { id: 'all', label: 'All' },
  { id: 'swap', label: 'Swap' },
  { id: 'collect', label: 'Collect' },
];

function Example() {
  const [activeTab, setActiveTab] = useState(tabs[0]);
  return (
    <TabbedChips
      activeTab={activeTab}
      onChange={setActiveTab}
      tabs={tabs}
      TabComponent={CustomTab}
    />
  );
}

render(<Example />);

Is this page useful?

Coinbase Design is an open-source, adaptable system of guidelines, components, and tools that aid the best practices of user interface design for crypto products.