Skip to content

shadcn.io is an independent project built on shadcn/ui

Free

View Switcher Button Group for React

A view switch for Grid, List, and Cards, as labels and as icons, sharing one view that starts on Grid.

One layout choice, two rows, starting on Grid. The first labels Grid, List, and Cards. The second uses icons for the same three, and each icon button has a name. Picking List fills List in both rows. The layout on the page does not change.

Put this at the top of a file list or a product grid. Arrow keys in one stop are a Toggle Group. In React, Base UI does not switch the layout for you.

Preview

Preview width

Installation

Sign in to get your install command
Free with an account. It carries your token, so the CLI installs everything in one go.
Use email insteadNo credit card

Anatomy

Button Group anatomy: root, group, button, separator

Usage

<div className="flex flex-col gap-4">
  <ButtonGroup aria-label="View">
    {VIEWS.map((item) => (
      <Button
        aria-pressed={view === item.value}
        key={item.value}
        onClick={handleSelect}
        size="sm"
        value={item.value}
        variant={view === item.value ? "default" : "outline"}
      >
        <item.icon aria-hidden="true" data-icon="inline-start" />
        {item.label}
      </Button>
    ))}
  </ButtonGroup>
  <ButtonGroup aria-label="View">
    {VIEWS.map((item) => (
      <Button
        aria-label={item.label}
        aria-pressed={view === item.value}
        key={item.value}
        onClick={handleSelect}
        size="icon-sm"
        value={item.value}
        variant={view === item.value ? "default" : "outline"}
      >
        <item.icon aria-hidden="true" />
      </Button>
    ))}
  </ButtonGroup>
</div>

FAQ

How is the view tracked?

One value, starting at grid. Each button carries its own value, and pressing it stores that value. The matching button gets variant="default".

Why do both rows change together?

They read the same value. Show the labeled row where there's room and the icon row in a toolbar.

How are the icon-only buttons named?

Each has an aria-label, Grid, List or Cards, and size="icon-sm". The icons have aria-hidden="true".

How is the active view announced?

Its button sets aria-pressed, so a screen reader reads it as pressed.

How do I remember the choice?

Save the value to local storage or the URL when it changes, and read it back as the starting value. The demo always starts on Grid.

Does the demo change a layout?

No. Render your items as a grid, list or cards from the value.

API Reference

Button Group

Sets aria-label="View". The rest is on the Button Group page.

Button

Sets aria-pressed={view === item.value}, onClick={handleSelect}, size="sm", value={item.value}, variant={view === item.value ? "default" : "outline"} and aria-label={item.label}. The rest is on the Button page.