Tabs

The Tab component provides a means to switch between different but contextually related panels.

Examples

Tabs

Tab 1 content (no padding or margins so consumer can control desired gutters) (no padding or margins so consumer can control desired gutters)

Try tabbing into this. You'll be able to tab through the links, and then shift-tab back out. To get to the next tab you use the arrow key and enter|space to select. This is consistent with wai-aria practices aria manual activation example.

Random yahoo link 1 and random yahoo link 2. Just testing some links out :)

Panel 1

Vertical Tabs

Vertical tab 1 content (no padding or margins so consumer can control desired gutters) (no padding or margins so consumer can control desired gutters)

Now you'll use the up and down arrows. Home and end still work the same. Random yahoo link 1 and random yahoo link 2. Just testing some links out :)

Disabled Tabs

Panel 1

Disabled Options

Disabled options only make sense if you do NOT supply isDisabled (as that disabled all the tab buttons), and you wish to selectively disable certain buttons. Here we've disabled the second and fourth tabs. Keyboard navigation skips the disabled tabs.

Custom

Custom tabs using an AgnosticUI button requires two things:

  • You need to use the tabType="custom" prop on the tab
  • You need to use the type="faux" prop on the button. This is required because the tabs are already buttons so you'd have a nested button a11y violation otherwise.

Tab 1 content (no padding or margins so consumer can control desired gutters) (no padding or margins so consumer can control desired gutters)

Try tabbing into this. You'll be able to tab through the links, and then shift-tab back out. To get to the next tab you use the arrow key and enter|space to select. This is consistent with wai-aria practices aria manual activation example.

Random yahoo link 1 and random yahoo link 2. Just testing some links out :)

Usage

astro logo

React, Vue, and Svelte examples on a single playground page 🚀 💥

react logoReact

View source
import "agnostic-react/dist/common.min.css";
import "agnostic-react/dist/esm/index.css";
import { Tabs, TabButton, TabPanel  } from "agnostic-react";

/**
 * This is a contrived setup to support the example, but,
 * we * DO need to ensure IDs (and ARIA IDs) are unique.
 */
const tabButtons = [];
const tabPanels = [];

[...Array(6)].forEach((_, i) => {
  tabButtons[i] = [
    <TabButton controlsPanelId={`panel1-${i}`} key={1}>
      Tab 1
    </TabButton>,
    <TabButton controlsPanelId={`panel2-${i}`} key={2}>
      Tab 2
    </TabButton>,
    <TabButton controlsPanelId={`panel3-${i}`} key={3}>
      Tab 3
    </TabButton>,
    <TabButton controlsPanelId={`panel4-${i}`} key={4}>
      Tab 4
    </TabButton>,
    <TabButton controlsPanelId={`panel5-${i}`} key={5}>
      Tab 5
    </TabButton>,
  ];

  tabPanels[i] = [
    <TabPanel id={`panel1-${i}`} key={1}>
      <p>Tab 1 content (no padding or margins so consumer can control desired gutters)</p>
    </TabPanel>,
    <TabPanel id={`panel2-${i}`} key={2}>
      <p>Tab 2 content (no padding or margins so consumer can control desired gutters)</p>
    </TabPanel>,
    <TabPanel id={`panel3-${i}`} key={3}>
      <p>Tab 3 content</p>
    </TabPanel>,
    <TabPanel id={`panel4-${i}`} key={4}>
      <p>Tab 4 content</p>
    </TabPanel>,
    <TabPanel id={`panel5-${i}`} key={5}>
      <p>Tab 5 content</p>
    </TabPanel>,
  ];
});
export const YourComponent = () => (
  <>
    <h2>Tabs</h2>
    <section className="mbs24 mbe40">
      <Tabs tabButtons={tabButtons[0]} tabPanels={tabPanels[0]} />
    </section>
    <h2>Tabs Large</h2>
    <section className="mbs24 mbe40">
      <Tabs size="large" tabButtons={tabButtons[1]} tabPanels={tabPanels[1]} />
    </section>
    <h2>Tabs XLarge</h2>
    <section className="mbs24 mbe40">
      <Tabs size="xlarge" tabButtons={tabButtons[2]} tabPanels={tabPanels[2]} />
    </section>
    <h2>Tabs Vertical</h2>
    <section className="mbs24 mbe40">
      <Tabs isVerticalOrientation tabButtons={tabButtons[3]} tabPanels={tabPanels[3]} />
    </section>
    <h2>Tabs Disabled</h2>
    <section className="mbs24 mbe40">
      <Tabs isDisabled tabButtons={tabButtons[4]} tabPanels={tabPanels[4]} />
    </section>
    <h2>Tabs Disabled Options</h2>
    <section className="mbs24 mbe40">
      <Tabs disabledOptions={[2,3]} tabButtons={tabButtons[5]} tabPanels={tabPanels[5]} />
    </section>
  </>
);

React: component source, storybook tests

Vue 3 logoVue 3

View source
<template>
  <section class="mbe40">
    <div class="h4 mbe32 flex items-center">
      <img
        width="24"
        height="24"
        src="/assets/Vue-icon.svg"
        alt="Vue logo"
        class="mie12"
      />Vue 3 Close
    </div>
    <div class="mbs24 mbe16">
      <Tabs>
        <template #tab-1>
          Tab 1
        </template>
        <template #panel-1>
          <p>Tab 1 content (no padding or margins so consumer can control desired gutters) (no padding or margins so consumer can control desired gutters)</p>
          <p>
            Try tabbing into this. You'll be able to tab through the links, and then shift-tab back out. To get to the
            next tab you use the arrow key and enter|space to select. This is consistent with
            wai-aria practices <a href="https://www.w3.org/TR/wai-aria-practices/examples/tabs/tabs-2/tabs.html">
              aria manual activation example</a>.
          </p>
          <p>Random <a href="www.yahoo.com">yahoo link 1</a> and random <a href="www.yahoo.com">yahoo link 2</a>. Just testing some links out :)</p>
        </template>
        <template #tab-2>
          Tab 2
        </template>
        <template #panel-2>
          <p>Random <a href="www.yahoo.com">yahoo link 1</a> and random <a href="www.yahoo.com">yahoo link 2</a>. Just testing some links out :)</p>
        </template>
        <template #tab-foo>
          Tab Foo
        </template>
        <template #panel-foo>
          Panel Foo content (no padding or margins so consumer can control desired gutters)
        </template>
        <template #tab-bar>
          Tab Bar
        </template>
        <template #panel-bar>
          Panel Bar
        </template>
      </Tabs>
    </div>
    <div class="mbs32 mbe16">
      <Tabs size="large">
        <template #tab-11>
          Tab 1
        </template>
        <template #panel-11>
          Panel 1
        </template>
        <template #tab-12>
          Tab 2
        </template>
        <template #panel-12>
          Panel 2
        </template>
      </Tabs>
    </div>
    <h2>Vertical Tabs</h2>
    <div class="mbs48 mbe16">
      <Tabs is-vertical>
        <template #tab-13>
          Tab 1
        </template>
        <template #panel-13>
          <p>Vertical tab 1 content (no padding or margins so consumer can control desired gutters) (no padding or margins so consumer can control desired gutters)</p>
          <p>Now you'll use the up and down arrows. Home and end still work the same. Random <a href="www.yahoo.com">yahoo link 1</a> and random <a href="www.yahoo.com">yahoo link 2</a>. Just testing some links out :)</p>
        </template>
        <template #tab-14>
          Tab 2
        </template>
        <template #panel-14>
          Vertical tab 2 content (no padding or margins so consumer can control desired gutters)
        </template>
      </Tabs>
    </div>
    <h2>Disabled Tabs</h2>
    <div class="mbs48 mbe16">
      <Tabs is-disabled>
        <template #tab-15>
          Tab 1
        </template>
        <template #panel-15>
          Panel 1
        </template>
        <template #tab-16>
          Tab 1
        </template>
        <template #panel-16>
          Panel 2
        </template>
      </Tabs>
    </div>
    <h2>Disabled Options</h2>
    <div class="mbs12 mbe16">
      <Tabs :disabled-options="disabledOptions">
        <template #tab-17>
          Tab 1
        </template>
        <template #panel-17>
          Disabled options only make sense if you do NOT supply isDisabled (as
          that disabled all the tab buttons), and you wish to selectively disable certain buttons.
          Here we've disabled the second and fourth tabs. Keyboard navigation skips the disabled tabs.
        </template>
        <template #tab-18>
          Tab 2
        </template>
        <template #panel-18>
          Panel 2
        </template>
        <template #tab-19>
          Tab 3
        </template>
        <template #panel-19>
          Panel 3
        </template>
        <template #tab-20>
          Tab 4
        </template>
        <template #panel-20>
          Panel 4
        </template>
        <template #tab-21>
          Tab 5
        </template>
        <template #panel-21>
          Panel 5
        </template>
      </Tabs>
    </div>
    <h2>Custom</h2>
    <div class="mbs12 mbe16">
      <div>
        <p class="mbe12">
          Custom tabs using an AgnosticUI button requires two things:
        </p>
        <ul class="mbe24">
          <li>You need to use the <code>tabType="custom"</code> prop on the tab</li>
          <li>You need to use the <code>type="faux"</code> prop on the button. This is required because the tabs are already buttons so you'd have a nested button a11y violation otherwise.</li>
        </ul>
        <Tabs tab-type="custom">
          <template #tab-22>
            <Button
              type="faux"
              mode="primary"
              is-bordered
            >
              Tab One
            </Button>
          </template>
          <template #panel-22>
            <p>Tab 1 content (no padding or margins so consumer can control desired gutters) (no padding or margins so consumer can control desired gutters)</p>
            <p>
              Try tabbing into this. You'll be able to tab through the links, and then shift-tab back out. To get to the
              next tab you use the arrow key and enter|space to select. This is consistent with
              wai-aria practices <a href="https://www.w3.org/TR/wai-aria-practices/examples/tabs/tabs-2/tabs.html">
                aria manual activation example</a>.
            </p>
            <p>Random <a href="www.yahoo.com">yahoo link 1</a> and random <a href="www.yahoo.com">yahoo link 2</a>. Just testing some links out :)</p>
          </template>
          <template #tab-23>
            <Button
              type="faux"
              mode="primary"
              is-bordered
            >
              Tab Two
            </Button>
          </template>
          <template #panel-23>
            Tab 2 content (no padding or margins so consumer can control desired gutters)
          </template>
        </Tabs>
      </div>
    </div>
  </section>
</template>

<script setup>
// Components CSS
import "agnostic-vue/dist/index.css";
import { Button, Tabs } from "agnostic-vue";
const disabledOptions = ["tab-18", "tab-20"];
</script>

Vue 3: component source, storybook tests

Svelte logoSvelte

The Svelte Tabs component utilizes content projection by means of passing an array of custom components that provide the corresponding panel content. See the following example:

View source
<script>
  // TabPanel1.svelte
  export let tabindex = 0;
</script>
<div id="panel-1" class="tab-panel" role="tabpanel" tabindex="{tabindex}">
  <h2>Tab 1</h2>
  <p>You can use any valid template content in the panel</p>
</div>

You will create as many of these panel components as your use case requires, and then import them and use as follows:

<script>
  import 'agnostic-svelte/css/common.min.css';
  import { Tabs } from "agnostic-svelte";
  import Tab1 from "path/to/your/TabPanel1.svelte";
  import Tab2 from "path/to/your/TabPanel2.svelte";
  import Tab3 from "path/to/your/TabPanel3.svelte";
</script>
<section class="mbe24">
  <Tabs size="large" tabs={[
    {
      title: "Tab 1",
      ariaControls: "panel-1",
      tabPanelComponent: Tab1,
    },
    {
      title: "Tab 2",
      ariaControls: "panel-2",
      tabPanelComponent: Tab2,
    },
    {
      title: "Tab 3",
      ariaControls: "panel-3",
      tabPanelComponent: Tab3,
    },
  ]}></Tabs>
</section>

Note, you can also project custom buttons. It's a bit more advanced but start here if that's a requirement.

Svelte: component source, storybook tests

Angular logoAngular (Experimental)

View source

In your Angular configuration (likely angular.json) ensure you're including the common AgnosticUI styles:

"styles": ["agnostic-angular/common.min.css"],

Add AgnosticUI's AgModule module:



 





 





import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AgModule } from 'agnostic-angular';

import { AppComponent } from './app.component';

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule, AgModule],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}

Now you can use in your components:

import { Component } from '@angular/core';

@Component({
  selector: 'your-component',
  template: `<section>
    <ag-tabs>
      <ag-tab-panel panelId="panel1"
                    tabButtonTitle="Tab 1">
        Tab 1 content (no padding or margins so consumer can control desired gutters) (no padding or margins so consumer
        can control desired gutters)
      </ag-tab-panel>
      <ag-tab-panel panelId="panel2"
                    tabButtonTitle="Tab 2">
        Tab 2 content (no padding or margins so consumer can control desired gutters) (no padding or margins so consumer
        can control desired gutters)
      </ag-tab-panel>
    </ag-tabs>
  </section>
  <section>
    <ag-tabs [isVerticalOrientation]="true">
      <ag-tab-panel panelId="panel3"
                    tabButtonTitle="Tab 1">
        Tab 1 content (no padding or margins so consumer can control desired gutters) (no padding or margins so consumer
        can control desired gutters)
      </ag-tab-panel>
      <ag-tab-panel panelId="panel4"
                    tabButtonTitle="Tab 2">
        Tab 2 content (no padding or margins so consumer can control desired gutters) (no padding or margins so consumer
        can control desired gutters)
      </ag-tab-panel>
    </ag-tabs>
  </section>
  <section>
    <ag-tabs size="xlarge"
             [disabledOptions]="['Tab 2', 'Tab 4']">
      <ag-tab-panel panelId="panel11"
                    tabButtonTitle="Tab 1">
        Tab 1 content (no padding or margins so consumer can control desired gutters)
      </ag-tab-panel>
      <ag-tab-panel panelId="panel12"
                    tabButtonTitle="Tab 2">
        Tab 2 content (no padding or margins so consumer can control desired gutters)
      </ag-tab-panel>
      <ag-tab-panel panelId="panel13"
                    tabButtonTitle="Tab 3">
        Tab 3 content
      </ag-tab-panel>
      <ag-tab-panel panelId="panel14"
                    tabButtonTitle="Tab 4">
        Tab 4 content
      </ag-tab-panel>
      <ag-tab-panel panelId="panel15"
                    tabButtonTitle="Tab 5">
        Tab 5 content
      </ag-tab-panel>
    </ag-tabs>
  </section>
  <section>
    <ag-tabs [tabButtonTemplate]="tabButtonTemplate">
      <ng-template #tabButtonTemplate
                   let-panel
                   let-idx="index">
        <button #tabButton
                role="tab"
                [class.active]="panel.isActive"
                style="background: transparent; border: none; margin-right: -1px;"
                [attr.aria-controls]="panel.panelId"
                [attr.aria-selected]="panel.isActive"
                [attr.tab-index]="panel.isActive ? 0 : -1">
          <ag-button type="faux"
                     [isBordered]="true"
                     mode="primary">
            {{ panel.tabButtonTitle }}
          </ag-button>
        </button>
      </ng-template>
      <ag-tab-panel panelId="panel16"
                    tabButtonTitle="Tab 1">
        <p>Custom tab buttons can be achieved by using your own button (which can be AgnosticUI Buttons) using
          <i>ngTemplateOutlet</i> of <i>#tabButtonTemplate</i> as we have done here.
        </p>
        <br />
        <p>Note, that it's important to pass the <i>type="faux"</i> prop if you use an AgnosticUI Button (or just use
          a <i>div</i> if you're making your own). Ultimately, we want a <i>div</i> that is styled like a button.
          This "tab button div" will get wrapped internally by <i>ag-tabs</i> with a button of its own. Doing this
          prevents an a11y violation of nesting focusable elements.</p>
        <br />
        <p>You will also want to give these buttons the role of <i>tab</i> and utilize the <i>aria-selected</i>
          attribute as we've done here (TODO -- link to GitHub source).</p>
      </ag-tab-panel>
      <ag-tab-panel panelId="panel17"
                    tabButtonTitle="Tab 2">
        Tab 2 content (no padding or margins so consumer can control desired gutters)
      </ag-tab-panel>
    </ag-tabs>
  </section>`
})
export class YourComponent {}

Angular: component source, storybook tests

Storybook

You can run the framework Storybooks and see live examples for React, Vue 3, Svelte, Astro, and Angular (experimental). The following will set up Storybook and run locally:

How to run Storybook
git clone git@github.com:AgnosticUI/agnosticui.git
cd agnosticui/<PACKAGE_NAME> && npm i # e.g. cd agnosticui/agnostic-react && npm i
npm run storybook

See Running Storybook.