Workday Canvas

Tabs

Allows the user to alternate between related views while remaining in the same context.

v14.2.34
Install
yarn add @workday/canvas-kit-react

Anatomy

Image of a Card with annotation markers.

  1. Active Tab: The tab that is selected and currently displaying it’s contents.
  2. Inactive Tab: All of the other unselected, available tabs.
  3. Active Chip: Highlights the active tab.
  4. Divider Line: Spans the width of the entire tabs container separating the tab set from its contents.
  5. Overflow Menu: Enable overflow menu when there isn’t enough space for “more” tab items.

Usage Guidance

  • Tabs are used to help the user navigate through information while remaining in context of the page. This is the most important reason to use Tabs. Users should not navigate to a different page when interacting with Tabs.
  • Content that is grouped within a Tab should always be related so that the user knows what to expect when they navigate each Tab. Never force users to switch back and forth between Tabs to complete a task.
  • Tab labels should be concise, scannable, and descriptive of their contents. Avoid using multiple words for Tab labels. Never use icons. To reduce cognitive load and a cluttered UI, avoid using more than 6 Tabs.
  • Tabs should directly relate to the page section they’re within.
  • The Tab divider line should span the full width of its container and create a clear distinction between what’s under the Tab sections and the rest of the page.
  • Do not use Tabs as a primary form of navigation.
  • When there are too many tabs to display at once, use the overflow menu to indicate that more Tabs are available.
  • The recommended minimum width of a tab is 88px, standard padding to the left and right is 24px, and minimum padding is 16px.
  • You can place most components within Tabs, because they operate like mini-pages. For example, Grids, Field Sets, and Prompts can all be added within a tabbed page.
  • You can use fixed Tabs, especially when the contents of a tab is scrollable.

When To Use

  • When content can be grouped into logical sections in order to avoid overwhelming the user with a lot of information. Tabs should cater for distinct groups of information.
  • To alternate between two or more sections of organized content while keeping the user in-context of the greater UI.
  • Use Tabs to help section off and organize related content.
  • When you have an abundance of related content to house in a small container.

When To Use Something Else

  • Consider an alternative solution if the information within multiple Tabs needs to be viewed simultaneously. Tabs are not suitable for comparing information or data, as it forces the user to rely on short term memory when switching back and forth between Tabs.
  • Consider using a more prominent, alternative form of navigation when there is a need for more than 6 or 7 Tabs.
  • Avoid using Tabs within a card UI.
  • If the content on a page is cohesive and doesn’t take up too much space, Tabs are likely unnecessary.
  • When Tabs are already being used on the page.

Variations

TypePurpose
Default/StandardShould be used in most use cases for tabs.
Full-WidthExclusively used in smaller containers ~300-400px width (e.g. within side panels or responsive UI).
  • No margin between Tabs. Tab widths are evenly proportioned, defined by its container.
  • Active chip spans full-width of the active Tab.
  • 2-3 Tabs max for this variation.
  • Do not use on large desktop experiences. More suitable for container widths less than 400px.
Wrapped TabsEdge case for when there is a requirement to have lengthy or multiple words for a Tab label.
  • Max of two lines wrapped.
  • This variation increases the overall Tab set height.

Touch Based Behavior

Touch Based behavior for Tab compnents on web adapt and mimic the mobile experience to allow overflow when the tab text stretches past the width of the users’ screen. These Tabs support mobile behavior based on modality and help support WebView users. Touch Based behavior for Tabs will switch from displaying an overflow menu to become scrollable from left to right in order to replicate a mobile experience when viewing a WebView screen (not a native mobile screen) on a mobile device.

States and Behavior

  • By default, Tabs should always contain one Tab with an active state.
  • Only one Tab can have an active state at a time.
  • The inactive state of a Tab can inherit a hover, focus, and selected state.
  • The active state of a Tab can only inherit a focus state.

Examples

Basic Example

Tabs includes a container Tabs component and the following subcomponents which can be composed in a variety of ways: Tabs.List, Tabs.Item and Tabs.Panel. It follows the W3 Tabs specification.

In this example, we set up a basic Tabs component with five tabs. This example uses a static API that does not support overflow.

Contents of First Tab
Contents of Second Tab
Contents of Third Tab
Contents of Fourth Tab
Contents of Fifth Tab
import {space} from '@workday/canvas-kit-react/tokens';

import {Tabs} from '@workday/canvas-kit-react/tabs';

export default () => {
  return (
    <Tabs>
      <Tabs.List>
        <Tabs.Item>First Tab</Tabs.Item>
        <Tabs.Item>Second Tab</Tabs.Item>
        <Tabs.Item>Third Tab</Tabs.Item>
        <Tabs.Item>Fourth Tab</Tabs.Item>
        <Tabs.Item>Fifth Tab</Tabs.Item>
      </Tabs.List>
      <div style={{marginTop: space.m}}>
        <Tabs.Panel>Contents of First Tab</Tabs.Panel>
        <Tabs.Panel>Contents of Second Tab</Tabs.Panel>
        <Tabs.Panel>Contents of Third Tab</Tabs.Panel>
        <Tabs.Panel>Contents of Fourth Tab</Tabs.Panel>
        <Tabs.Panel>Contents of Fifth Tab</Tabs.Panel>
      </div>
    </Tabs>
  );
};

Overflow Tabs

Tabs is a responsive component based on the width of its container. If the rendered tabs exceed the width of the Tabs.List, an overflow menu will be rendered. This only works against the dynamic API where you give the TabsModel an array of items to be rendered. The dynamic API handles the React key for you based on the item’s identifier. The dynamic API requires either an id on each item object or a getId function that returns an identifier based on the item. The below example uses an id property on each item.

The dynamic API takes in any object, but since nothing is known about your object, a render prop is necessary to instruct a list how it should render.

Contents of First Tab

Change Tabs container size

import React from 'react';

import {Tabs, useTabsModel} from '@workday/canvas-kit-react/tabs';
import {SegmentedControl} from '@workday/canvas-kit-preview-react/segmented-control';
import {Box} from '@workday/canvas-kit-react/layout';

type MyTabItem = {
  id: string;
  text: React.ReactNode;
  contents: string;
};

export default () => {
  const [items] = React.useState<MyTabItem[]>([
    {id: 'first', text: 'First Tab', contents: 'Contents of First Tab'},
    {id: 'second', text: 'Second Tab', contents: 'Contents of Second Tab'},
    {id: 'third', text: 'Third Tab', contents: 'Contents of Third Tab'},
    {id: 'fourth', text: 'Fourth Tab', contents: 'Contents of Fourth Tab'},
    {id: 'fifth', text: 'Fifth Tab', contents: 'Contents of Fifth Tab'},
    {id: 'sixth', text: 'Sixth Tab', contents: 'Contents of Sixth Tab'},
    {id: 'seventh', text: 'Seventh Tab', contents: 'Contents of Seventh Tab'},
  ]);
  const model = useTabsModel({
    items,
  });
  const [containerWidth, setContainerWidth] = React.useState('100%');
  return (
    <div>
      <Box width={containerWidth} marginBottom="xl">
        <Tabs model={model}>
          <Tabs.List overflowButton={<Tabs.OverflowButton>More</Tabs.OverflowButton>}>
            {(item: MyTabItem) => <Tabs.Item>{item.text}</Tabs.Item>}
          </Tabs.List>
          <Tabs.Menu.Popper>
            <Tabs.Menu.Card maxWidth={300} maxHeight={200}>
              <Tabs.Menu.List>
                {(item: MyTabItem) => <Tabs.Menu.Item>{item.text}</Tabs.Menu.Item>}
              </Tabs.Menu.List>
            </Tabs.Menu.Card>
          </Tabs.Menu.Popper>
          <Tabs.Panels>
            {(item: MyTabItem) => <Tabs.Panel marginTop="m">{item.contents}</Tabs.Panel>}
          </Tabs.Panels>
        </Tabs>
      </Box>
      <hr />
      <h4>Change Tabs container size</h4>
      <SegmentedControl onSelect={data => setContainerWidth(data.id)}>
        <SegmentedControl.List aria-label="container width control">
          <SegmentedControl.Item data-id="100%">100%</SegmentedControl.Item>
          <SegmentedControl.Item data-id="500px">500px</SegmentedControl.Item>
          <SegmentedControl.Item data-id="360px">360px</SegmentedControl.Item>
          <SegmentedControl.Item data-id="150px">150px</SegmentedControl.Item>
        </SegmentedControl.List>
      </SegmentedControl>
    </div>
  );
};

Hoisted Model

By default, Tabs will create and use its own model internally. Alternatively, you may configure your own model with useTabsModel and pass it to Tabs via the model prop. This pattern is referred to as hoisting the model and provides direct access to its state and events outside of the Tabs component.

In this example, we set up external observation of the model state and create an external button to trigger an event to change the active tab.

import React from 'react';
import {space} from '@workday/canvas-kit-react/tokens';

import {SecondaryButton} from '@workday/canvas-kit-react/button';
import {Tabs, useTabsModel} from '@workday/canvas-kit-react/tabs';

export default () => {
  const model = useTabsModel({
    onSelect(data, prevState) {
      console.log('Selected Tab', data.id, prevState);
    },
  });

  return (
    <>
      <Tabs model={model}>
        <Tabs.List>
          <Tabs.Item data-id="first">First Tab</Tabs.Item>
          <Tabs.Item data-id="second">Second Tab</Tabs.Item>
          <Tabs.Item data-id="third">Third Tab</Tabs.Item>
        </Tabs.List>
        <div style={{marginTop: space.m}}>
          <Tabs.Panel data-id="first">Contents of First Tab</Tabs.Panel>
          <Tabs.Panel data-id="second">Contents of Second Tab</Tabs.Panel>
          <Tabs.Panel data-id="third">Contents of Third Tab</Tabs.Panel>
        </div>
      </Tabs>
      <SecondaryButton
        onClick={() => {
          model.events.select({id: 'third'});
        }}
      >
        Select Third Tab
      </SecondaryButton>
    </>
  );
};

Named Tabs

Tabs.Item and Tabs.Panel both take an optional data-id attribute that is used for the onActivate callback. This example is identical to the Basic Example, but with tabs named using data-id for the Tabs.Item and Tabs.Panel subcomponents.

import {space} from '@workday/canvas-kit-react/tokens';

import {Tabs} from '@workday/canvas-kit-react/tabs';

export default () => {
  return (
    <Tabs>
      <Tabs.List>
        <Tabs.Item data-id="first">First Tab</Tabs.Item>
        <Tabs.Item data-id="second">Second Tab</Tabs.Item>
        <Tabs.Item data-id="third">Third Tab</Tabs.Item>
        <Tabs.Item data-id="fourth">Fourth Tab</Tabs.Item>
        <Tabs.Item data-id="fifth">Fifth Tab</Tabs.Item>
      </Tabs.List>
      <div style={{marginTop: space.m}}>
        <Tabs.Panel data-id="first">Contents of First Tab</Tabs.Panel>
        <Tabs.Panel data-id="second">Contents of Second Tab</Tabs.Panel>
        <Tabs.Panel data-id="third">Contents of Third Tab</Tabs.Panel>
        <Tabs.Panel data-id="fourth">Contents of Fourth Tab</Tabs.Panel>
        <Tabs.Panel data-id="fifth">Contents of Fifth Tab</Tabs.Panel>
      </div>
    </Tabs>
  );
};

Right-to-Left (RTL)

Tabs supports right-to-left languages when specified in the CanvasProvider theme.

תוכן הראשון
תוכן השני
תוכן השלישי
תוכן הרביעי
תוכן החמישי
import {space} from '@workday/canvas-kit-react/tokens';
import {CanvasProvider} from '@workday/canvas-kit-react/common';

import {Tabs} from '@workday/canvas-kit-react/tabs';

export default () => {
  return (
    <CanvasProvider dir="rtl">
      <Tabs>
        <Tabs.List>
          <Tabs.Item>ראשון</Tabs.Item>
          <Tabs.Item>שְׁנִיָה</Tabs.Item>
          <Tabs.Item>שְׁלִישִׁי</Tabs.Item>
          <Tabs.Item>רביעי</Tabs.Item>
          <Tabs.Item>חמישי</Tabs.Item>
        </Tabs.List>
        <div style={{marginTop: space.m}}>
          <Tabs.Panel>תוכן הראשון</Tabs.Panel>
          <Tabs.Panel>תוכן השני</Tabs.Panel>
          <Tabs.Panel>תוכן השלישי</Tabs.Panel>
          <Tabs.Panel>תוכן הרביעי</Tabs.Panel>
          <Tabs.Panel>תוכן החמישי</Tabs.Panel>
        </div>
      </Tabs>
    </CanvasProvider>
  );
};

Disabled Tab

Set the disabled prop of a Tabs.Item to true to disable it.

Contents of First Tab
Contents of Disabled Tab
Contents of Third Tab
import {space} from '@workday/canvas-kit-react/tokens';

import {Tabs} from '@workday/canvas-kit-react/tabs';

export default () => {
  return (
    <Tabs>
      <Tabs.List>
        <Tabs.Item>First Tab</Tabs.Item>
        <Tabs.Item aria-disabled>Disabled Tab</Tabs.Item>
        <Tabs.Item>Third Tab</Tabs.Item>
      </Tabs.List>
      <div style={{marginTop: space.m}}>
        <Tabs.Panel>Contents of First Tab</Tabs.Panel>
        <Tabs.Panel>Contents of Disabled Tab</Tabs.Panel>
        <Tabs.Panel>Contents of Third Tab</Tabs.Panel>
      </div>
    </Tabs>
  );
};

Tab Icons

Tabs can have icons. Use the Icon and Text subcomponents.

Contents of First Tab
Contents of Second Tab
Contents of Third Tab
Contents of Fourth Tab
import React from 'react';

import {space} from '@workday/canvas-kit-react/tokens';
import {starIcon, searchIcon, selectIcon, shareIcon} from '@workday/canvas-system-icons-web';
import {Tabs} from '@workday/canvas-kit-react/tabs';

export default () => {
  return (
    <Tabs>
      <Tabs.List>
        <Tabs.Item>
          <Tabs.Item.Icon icon={starIcon} />
          <Tabs.Item.Text>First Tab</Tabs.Item.Text>
        </Tabs.Item>
        <Tabs.Item>
          <Tabs.Item.Icon icon={searchIcon} />
          <Tabs.Item.Text>Second Tab</Tabs.Item.Text>
        </Tabs.Item>
        <Tabs.Item>
          <Tabs.Item.Icon icon={selectIcon} />
          <Tabs.Item.Text>Third Tab</Tabs.Item.Text>
        </Tabs.Item>
        <Tabs.Item>
          <Tabs.Item.Icon icon={shareIcon} />
          <Tabs.Item.Text>Fourth Tab</Tabs.Item.Text>
        </Tabs.Item>
      </Tabs.List>
      <div style={{marginTop: space.m}}>
        <Tabs.Panel>Contents of First Tab</Tabs.Panel>
        <Tabs.Panel>Contents of Second Tab</Tabs.Panel>
        <Tabs.Panel>Contents of Third Tab</Tabs.Panel>
        <Tabs.Panel>Contents of Fourth Tab</Tabs.Panel>
      </div>
    </Tabs>
  );
};

Alternative Tab Stop

By default, tab panels are focusable for accessibility. If the contents of a tab panel have a focusable element, you may disable this default behavior by setting the tabIndex prop of Tabs.Panel to undefined. This example has a tab panel with a focusable button.


Contents of First Tab. The tab panel is no longer focusable, but the button is. It may be desirable to disable focus on the tab panel and allow focus to flow into the tab panel to the first focusable element.
Contents of Second Tab
Contents of Third Tab
import React from 'react';
import {space} from '@workday/canvas-kit-react/tokens';

import {Tabs} from '@workday/canvas-kit-react/tabs';

export default () => {
  return (
    <Tabs>
      <Tabs.List>
        <Tabs.Item>First Tab</Tabs.Item>
        <Tabs.Item>Second Tab</Tabs.Item>
        <Tabs.Item>Third Tab</Tabs.Item>
      </Tabs.List>
      <div style={{marginTop: space.m}}>
        <Tabs.Panel tabIndex={undefined}>
          <button>Focusable button</button>
          <br />
          Contents of First Tab. The tab panel is no longer focusable, but the button is. It may be
          desirable to disable focus on the tab panel and allow focus to flow into the tab panel to
          the first focusable element.
        </Tabs.Panel>
        <Tabs.Panel>Contents of Second Tab</Tabs.Panel>
        <Tabs.Panel>Contents of Third Tab</Tabs.Panel>
      </div>
    </Tabs>
  );
};

Single Tab Panel

The compound component pattern allows for advanced composition. For example, Tabs can be composed to have only a single Tabs.Panel using attribute overrides and callbacks. More information about attributes and callbacks can be found in the prop tables below for each subcomponent.

In this example, we use a hoisted model and the activeTab property of the state to show content from the contents object.

import React from 'react';
import {space} from '@workday/canvas-kit-react/tokens';

import {Tabs, useTabsModel} from '@workday/canvas-kit-react/tabs';

export default () => {
  const model = useTabsModel();

  const message = (
    <p>
      This example shows how to use a single tab panel. You must manually set the{' '}
      <code>hidden</code>, <code>aria-controls</code>, and <code>id</code> attributes of Tab item
      and Tab panel components
    </p>
  );

  const contents = {
    first: <div>Contents of First Tab {message}</div>,
    second: <div>Contents of Second Tab {message}</div>,
    third: <div>Contents of Third Tab {message}</div>,
  };

  return (
    <Tabs model={model}>
      <Tabs.List>
        <Tabs.Item data-id="first" aria-controls="mytab-panel">
          First Tab
        </Tabs.Item>
        <Tabs.Item data-id="second" aria-controls="mytab-panel">
          Second Tab
        </Tabs.Item>
        <Tabs.Item data-id="third" aria-controls="mytab-panel">
          Third Tab
        </Tabs.Item>
      </Tabs.List>
      <Tabs.Panel style={{marginTop: space.m}} hidden={undefined} id="mytab-panel">
        {contents[model.state.selectedIds[0]]}
      </Tabs.Panel>
    </Tabs>
  );
};

Dynamic Tabs

The Tabs.Item component takes in an optional index property if you want to specify the position of a tab. If not defined, by default it will append tabs to the end. In this example, our tabs are stored as an array in the state, and we have a fixed tab at the end that can add new tabs to that array.

Contents of Tab 1
import React from 'react';

import {slugify} from '@workday/canvas-kit-react/common';
import {isCursor} from '@workday/canvas-kit-react/collection';
import {Tabs, useTabsModel} from '@workday/canvas-kit-react/tabs';

type Tab = {
  tab: string;
  id: string;
};

export default () => {
  const [tabs, setTabs] = React.useState<Tab[]>([
    {tab: 'Tab 1', id: 'tab-1'},
    {tab: 'Tab 2', id: 'tab-2'},
    {tab: 'Tab 3', id: 'tab-3'},
    {tab: 'Add Tab', id: 'add'},
  ]);
  const addedRef = React.useRef(tabs.length - 1);
  const model = useTabsModel({
    items: tabs,
    shouldSelect: data => data.id !== 'add',
  });

  // A ref of the model for the render functions to work around the caching done to lists
  const modelRef = React.useRef(model);
  modelRef.current = model;

  /**
   * Helper function that should be called when an item is programmatically removed. The following
   * side effects depend on state of the model:
   * * **Item is focused**: Focus will be moved to next item in the list
   * * **Item is selected**: Selection will be moved to the next item in the list
   * @param id The id of the item that will be removed
   */
  const removeItem = <T extends unknown>(id: string, model: ReturnType<typeof useTabsModel>) => {
    const index = model.state.items.findIndex(item => isCursor(model.state, item.id));
    const nextIndex = index === model.state.items.length - 1 ? index - 1 : index + 1;
    const nextId = model.state.items[nextIndex].id;
    if (model.state.selectedIds[0] === id) {
      // We're removing the currently selected item. Select next item
      model.events.select({id: nextId});
    }
    if (isCursor(model.state, id)) {
      // We're removing the currently focused item. Focus next item
      model.events.goTo({id: nextId});

      // wait for stabilization of state
      requestAnimationFrame(() => {
        document
          .querySelector<HTMLElement>(`[id="${slugify(`${model.state.id}-${nextId}`)}"]`)
          ?.focus();
      });
    }
  };

  const onKeyDown = (id: string) => (e: React.KeyboardEvent<HTMLElement>) => {
    if ((e.key === 'Delete' || e.key === 'Backspace') && id !== 'add') {
      setTabs(tabs.filter(item => item.id !== id));
      const model = modelRef.current;
      removeItem(id, model);
    }
  };

  const onClick = (id: string) => (e: React.MouseEvent) => {
    if (id === 'add') {
      addedRef.current += 1;
      setTabs(tabs => {
        const newTabs = tabs.slice(0, tabs.length - 1);
        const addTab = tabs.slice(-1);
        return newTabs.concat(
          {tab: `Tab ${addedRef.current}`, id: `tab-${addedRef.current}`},
          addTab
        );
      });
    }
  };

  return (
    <Tabs model={model}>
      <Tabs.List overflowButton={<Tabs.OverflowButton>More</Tabs.OverflowButton>}>
        {(item: Tab) => (
          <Tabs.Item onKeyDown={onKeyDown(item.id)} onClick={onClick(item.id)}>
            {item.tab}
          </Tabs.Item>
        )}
      </Tabs.List>
      <Tabs.Menu.Popper>
        <Tabs.Menu.Card maxWidth={300} maxHeight={200}>
          <Tabs.Menu.List>
            {(item: Tab) => <Tabs.Menu.Item>{item.tab}</Tabs.Menu.Item>}
          </Tabs.Menu.List>
        </Tabs.Menu.Card>
      </Tabs.Menu.Popper>
      <Tabs.Panels>
        {(item: Tab) => <Tabs.Panel marginTop="m">Contents of {item.tab}</Tabs.Panel>}
      </Tabs.Panels>
    </Tabs>
  );
};

Component API

Tabs

Tabs is a container component that is responsible for creating a {@link TabsModel } and sharing it with its subcomponents using React context. It does not represent a real element.

<Tabs onSelect={data => console.log('Selected tab', data.id)}>
  {child components}
</Tabs>

Alternatively, you may pass in a model using the hoisted model pattern.

const model = useTabsModel({
  onSelect(data) {
    console.log('Activated Tab', data.id);
  },
});

<Tabs model={model}>{child components}</Tabs>

See Configuring a Model for more information on model configuration.

Props

Props extend from . If a model is passed, props from TabsModelConfig are ignored.

NameTypeDescriptionDefault
childrenReactNode

The contents of the Tabs. Can be Tabs children or any valid elements.

model

Optional model to pass to the component. This will override the default model created for the component. This can be useful if you want to access to the state and events of the model, or if you have nested components of the same type and you need to override the model provided by React Context.

elemPropsHook(
  model: ,
  elemProps: TProps
) => HTML Attributes

Optional hook that receives the model and all props to be applied to the element. If you use this, it is your responsibility to return props, merging as appropriate. For example, returning an empty object will disable all elemProps hooks associated with this component. This allows finer control over a component without creating a new one.

Tabs.List

Tabs.List is a <div role="tablist"> element. It is a container for subcomponents.

<Tabs.List>{Tabs.Items}</Tabs.List>

Layout Component

Tabs.List supports all props from thelayout component.

Props

Props extend from div. Changing the as prop will change the element interface.

NameTypeDescriptionDefault
children ((item: T) => ReactNode) ReactNode

If items are passed to a TabsModel, the child of Tabs.List should be a render prop. The List will determine how and when the item will be rendered.

overflowButtonReactNode
cs

The cs prop takes in a single value or an array of values. You can pass the CSS class name returned by , or the result of and . If you're extending a component already using cs, you can merge that prop in as well. Any style that is passed to the cs prop will override style props. If you wish to have styles that are overridden by the css prop, or styles added via the styled API, use wherever elemProps is used. If your component needs to also handle style props, use {@link mergeStyles } instead.

import {handleCsProp} from '@workday/canvas-kit-styling';
import {mergeStyles} from '@workday/canvas-kit-react/layout';

// ...

// `handleCsProp` handles compat mode with Emotion's runtime APIs. `mergeStyles` has the same
// function signature, but adds support for style props.

return (
 <Element
   {...handleCsProp(elemProps, [
     myStyles,
     myModifiers({ size: 'medium' }),
     myVars({ backgroundColor: 'red' })
   ])}
 >
   {children}
 </Element>
)
maskImagestring
asReact.ElementType

Optional override of the default element used by the component. Any valid tag or Component. If you provided a Component, this component should forward the ref using React.forwardRefand spread extra props to a root element.

Note: Not all elements make sense and some elements may cause accessibility issues. Change this value with care.

div
refReact.Ref<R = div>

Optional ref. If the component represents an element, this ref will be a reference to the real DOM element of the component. If as is set to an element, it will be that element. If as is a component, the reference will be to that component (or element if the component uses React.forwardRef).

model

Optional model to pass to the component. This will override the default model created for the component. This can be useful if you want to access to the state and events of the model, or if you have nested components of the same type and you need to override the model provided by React Context.

elemPropsHook(
  model: ,
  elemProps: TProps
) => HTML Attributes

Optional hook that receives the model and all props to be applied to the element. If you use this, it is your responsibility to return props, merging as appropriate. For example, returning an empty object will disable all elemProps hooks associated with this component. This allows finer control over a component without creating a new one.

useTabsList

(
  ,
  (
    model: ,
    elemProps: {},
    ref: React.Ref
  ) => {
    role: 'tablist';
    aria-orientation: ;
  },
  ,
  
)

Tabs.Item

Tabs.Item is a <button role="tab"> element. Each Tabs.Item is associated with a Tabs.Panel either by a data-id attribute or by the position in the list. If a data-id is provided, it must match the data-id passed to the corresponding Tabs.Panel component.

<Tabs.Item data-id="first">First Tab</Tabs.Item>

Layout Component

Tabs.Item supports all props from thelayout component.

Props

Props extend from button. Changing the as prop will change the element interface.

NameTypeDescriptionDefault
indexnumber

Optionally pass index to tab item. This should be done if Tabs.Item components were created via a Array::map function. This index will ensure keyboard navigation works even if items are inserted out of order.

childrenReactNode

The contents of the tab item. This will be the accessible name of the tab for screen readers. Often, this is text. Icons are also supported. Using Tabs.Icon will render an icon that is not visible to screen readers and therefore the icon should not be necessary to understand the tab. In most circumstances, aria-label should not be used.

<Tabs.Item>First Tab</Tabs.Item>
<Tabs.Item>
  <Tabs.Icon icon={canvasIcon} />
  <Tabs.Text>Second Tab</Tabs.Text>
</Tabs.Item>
data-idstring

The identifier of the tab. This identifier will be used in change events and for initialTab. Must match the data-id of the associated tab panel. If this property is not provided, it will default to a string representation of the the zero-based index of the Tab when it was initialized.

idstring

Optional id. If not set, it will inherit the ID passed to the Tabs component and append the index at the end. Only set this for advanced cases.

aria-controlsstring

Part of the ARIA specification for tabs. This attributes links a role=tab to a role=tabpanel. This value must be the same as the associated id attribute of the tab panel. This is automatically set by the component and should only be used in advanced cases.

aria-selectedboolean

Part of the ARIA specification for tabs. Lets screen readers know which tab is active. This should either be true or undefined and never false. This is automatically set by the component and should only be used in advanced cases.

tabIndexnumber

Part of the ARIA specification for tabs. The currently active tab should not have a tabIndex set while all inactive tabs should have a tabIndex={-1}

cs

The cs prop takes in a single value or an array of values. You can pass the CSS class name returned by , or the result of and . If you're extending a component already using cs, you can merge that prop in as well. Any style that is passed to the cs prop will override style props. If you wish to have styles that are overridden by the css prop, or styles added via the styled API, use wherever elemProps is used. If your component needs to also handle style props, use {@link mergeStyles } instead.

import {handleCsProp} from '@workday/canvas-kit-styling';
import {mergeStyles} from '@workday/canvas-kit-react/layout';

// ...

// `handleCsProp` handles compat mode with Emotion's runtime APIs. `mergeStyles` has the same
// function signature, but adds support for style props.

return (
 <Element
   {...handleCsProp(elemProps, [
     myStyles,
     myModifiers({ size: 'medium' }),
     myVars({ backgroundColor: 'red' })
   ])}
 >
   {children}
 </Element>
)
asReact.ElementType

Optional override of the default element used by the component. Any valid tag or Component. If you provided a Component, this component should forward the ref using React.forwardRefand spread extra props to a root element.

Note: Not all elements make sense and some elements may cause accessibility issues. Change this value with care.

button
refReact.Ref<R = button>

Optional ref. If the component represents an element, this ref will be a reference to the real DOM element of the component. If as is set to an element, it will be that element. If as is a component, the reference will be to that component (or element if the component uses React.forwardRef).

model

Optional model to pass to the component. This will override the default model created for the component. This can be useful if you want to access to the state and events of the model, or if you have nested components of the same type and you need to override the model provided by React Context.

elemPropsHook(
  model: ,
  elemProps: TProps
) => HTML Attributes

Optional hook that receives the model and all props to be applied to the element. If you use this, it is your responsibility to return props, merging as appropriate. For example, returning an empty object will disable all elemProps hooks associated with this component. This allows finer control over a component without creating a new one.

useTabsItem

(
  (
    model: ,
    elemProps: {
      data-id: string;
    },
    ref: React.Ref
  ) => {
    type: 'button';
    role: 'tab';
    aria-selected: boolean;
    aria-controls: string;
  },
  ,
  ,
  ,
  
)

Tabs.Item.Icon

Layout Component

SystemIcon supports all props from thelayout component.

Props

Props extend from span. Changing the as prop will change the element interface.

NameTypeDescriptionDefault
icon

The icon to display from @workday/canvas-system-icons-web.

size number string

The size of the SystemIcon in px.

accentstring

The accent color of the SystemIcon. This overrides color.

accentHoverstring

The accent color of the SystemIcon on hover. This overrides colorHover.

backgroundstring

The background color of the SystemIcon.

backgroundHoverstring

The background color of the SystemIcon on hover.

colorstring

The color of the SystemIcon. This defines accent and fill. color may be overwritten by accent and fill.

colorHoverstring

The hover color of the SystemIcon. This defines accentHover and fillHover. colorHover may be overwritten by accentHover and fillHover.

fillstring

The fill color of the SystemIcon. This overrides color.

fillHoverstring

The fill color of the SystemIcon on hover. This overrides colorHover.

shouldMirrorboolean

If set to true, transform the SVG's x-axis to mirror the graphic. Use this if you want to always mirror the icon regardless of the content direction. If the SVG should mirror only when in an right-to-left language, use shouldMirrorInRTL instead.

false
shouldMirrorInRTLboolean

If set to true, transform the SVG's x-axis to mirror the graphic when the content direction is rtl. Icons don't have enough context to know if they should be mirrored in all cases. Setting this to true indicates the icon should be mirrored in right-to-left languages.

false
cs

The cs prop takes in a single value or an array of values. You can pass the CSS class name returned by , or the result of and . If you're extending a component already using cs, you can merge that prop in as well. Any style that is passed to the cs prop will override style props. If you wish to have styles that are overridden by the css prop, or styles added via the styled API, use wherever elemProps is used. If your component needs to also handle style props, use {@link mergeStyles } instead.

import {handleCsProp} from '@workday/canvas-kit-styling';
import {mergeStyles} from '@workday/canvas-kit-react/layout';

// ...

// `handleCsProp` handles compat mode with Emotion's runtime APIs. `mergeStyles` has the same
// function signature, but adds support for style props.

return (
 <Element
   {...handleCsProp(elemProps, [
     myStyles,
     myModifiers({ size: 'medium' }),
     myVars({ backgroundColor: 'red' })
   ])}
 >
   {children}
 </Element>
)
childrenReactNode
asReact.ElementType

Optional override of the default element used by the component. Any valid tag or Component. If you provided a Component, this component should forward the ref using React.forwardRefand spread extra props to a root element.

Note: Not all elements make sense and some elements may cause accessibility issues. Change this value with care.

span
refReact.Ref<R = span>

Optional ref. If the component represents an element, this ref will be a reference to the real DOM element of the component. If as is set to an element, it will be that element. If as is a component, the reference will be to that component (or element if the component uses React.forwardRef).

Tabs.Item.Text

Props

Props extend from span. Changing the as prop will change the element interface.

NameTypeDescriptionDefault
childrenReact.ReactNode
asReact.ElementType

Optional override of the default element used by the component. Any valid tag or Component. If you provided a Component, this component should forward the ref using React.forwardRefand spread extra props to a root element.

Note: Not all elements make sense and some elements may cause accessibility issues. Change this value with care.

span
refReact.Ref<R = span>

Optional ref. If the component represents an element, this ref will be a reference to the real DOM element of the component. If as is set to an element, it will be that element. If as is a component, the reference will be to that component (or element if the component uses React.forwardRef).

Tabs.Panel

Tabs.Panel is a <div role="tabpanel"> element. It is focusable by default. Each Tabs.Panel is controlled by a Tabs.Item either by a data-id attribute or by the position in the list. If a data-id is provided, it must match the data-id passed to the corresponding Tabs.Item component.

<Tabs.Panel data-id="first">
  {contents of tab panel}
</Tabs.Panel>

Layout Component

Tabs.Panel supports all props from thelayout component.

Props

Props extend from div. Changing the as prop will change the element interface.

NameTypeDescriptionDefault
childrenReactNode

The contents of the TabPanel.

data-idstring

The identifier of the tab. This identifier will be used in change events and for initialTab. Must match the data-id of the associated tab item. If this property is not provided, it will default to a string representation of the the zero-based index of the Tab when it was initialized.

tabIndexnumber

Part of the ARIA specification for tabs. By default, all tabpanel elements have a tabIndex of 0 which makes the whole content area receive focus. If you have a focusable item near the top of the tab panel content area, you may set tabIndex to undefined to prevent the tab panel element from receiving focus. Only do this is a child of the tab panel can receive focus.

cs

The cs prop takes in a single value or an array of values. You can pass the CSS class name returned by , or the result of and . If you're extending a component already using cs, you can merge that prop in as well. Any style that is passed to the cs prop will override style props. If you wish to have styles that are overridden by the css prop, or styles added via the styled API, use wherever elemProps is used. If your component needs to also handle style props, use {@link mergeStyles } instead.

import {handleCsProp} from '@workday/canvas-kit-styling';
import {mergeStyles} from '@workday/canvas-kit-react/layout';

// ...

// `handleCsProp` handles compat mode with Emotion's runtime APIs. `mergeStyles` has the same
// function signature, but adds support for style props.

return (
 <Element
   {...handleCsProp(elemProps, [
     myStyles,
     myModifiers({ size: 'medium' }),
     myVars({ backgroundColor: 'red' })
   ])}
 >
   {children}
 </Element>
)
asReact.ElementType

Optional override of the default element used by the component. Any valid tag or Component. If you provided a Component, this component should forward the ref using React.forwardRefand spread extra props to a root element.

Note: Not all elements make sense and some elements may cause accessibility issues. Change this value with care.

div
refReact.Ref<R = div>

Optional ref. If the component represents an element, this ref will be a reference to the real DOM element of the component. If as is set to an element, it will be that element. If as is a component, the reference will be to that component (or element if the component uses React.forwardRef).

model

Optional model to pass to the component. This will override the default model created for the component. This can be useful if you want to access to the state and events of the model, or if you have nested components of the same type and you need to override the model provided by React Context.

elemPropsHook(
  model: ,
  elemProps: TProps
) => HTML Attributes

Optional hook that receives the model and all props to be applied to the element. If you use this, it is your responsibility to return props, merging as appropriate. For example, returning an empty object will disable all elemProps hooks associated with this component. This allows finer control over a component without creating a new one.

useTabsPanel

(
  model: ,
  elemProps: {
    data-id: string;
  },
  ref: React.Ref
) => {
  role: 'tabpanel';
  aria-labelledby: string;
  hidden: boolean;
  id: string;
  tabIndex: 0;
}

Tabs.Panels

Optional subcomponent of tabs that makes dealing with dynamic tabs easier. Tabs.Panels only works if the model is given an array of items.

const items = [{ id: '1', name: 'first' }]
<Tabs items={items}>
  {tab list}
  <Tabs.Panels>
    {item => <Tabs.Panel>Contents of {item.name}</Tabs.Panel>}
  </Tabs.Panels>
</Tabs>

Props

NameTypeDescriptionDefault
children ((item: T) => ReactNode) ReactNode
model

Optional model to pass to the component. This will override the default model created for the component. This can be useful if you want to access to the state and events of the model, or if you have nested components of the same type and you need to override the model provided by React Context.

elemPropsHook(
  model: ,
  elemProps: TProps
) => HTML Attributes

Optional hook that receives the model and all props to be applied to the element. If you use this, it is your responsibility to return props, merging as appropriate. For example, returning an empty object will disable all elemProps hooks associated with this component. This allows finer control over a component without creating a new one.

Tabs.OverflowButton

The overflow button of a Tabs.List. When a tab list overflows, this overflow button is rendered. Overflow only works when the model is given an array of items.

<Tabs items={items}>
  <Tabs.List overflowButton={
    <Tabs.OverflowButton>More</Tabs.OverflowButton>
  }>
    {dynamic tab items}
  </Tabs.List>
</Tabs>

Props

Props extend from button. Changing the as prop will change the element interface.

NameTypeDescriptionDefault
childrenReactNode

The label text of the Tab.

asReact.ElementType

Optional override of the default element used by the component. Any valid tag or Component. If you provided a Component, this component should forward the ref using React.forwardRefand spread extra props to a root element.

Note: Not all elements make sense and some elements may cause accessibility issues. Change this value with care.

button
refReact.Ref<R = button>

Optional ref. If the component represents an element, this ref will be a reference to the real DOM element of the component. If as is set to an element, it will be that element. If as is a component, the reference will be to that component (or element if the component uses React.forwardRef).

model

Optional model to pass to the component. This will override the default model created for the component. This can be useful if you want to access to the state and events of the model, or if you have nested components of the same type and you need to override the model provided by React Context.

elemPropsHook(
  model: ,
  elemProps: TProps
) => HTML Attributes

Optional hook that receives the model and all props to be applied to the element. If you use this, it is your responsibility to return props, merging as appropriate. For example, returning an empty object will disable all elemProps hooks associated with this component. This allows finer control over a component without creating a new one.

useTabsOverflowButton

(
  (
    model: ,
    elemProps: {},
    ref: React.Ref
  ) => {
    aria-haspopup: true;
  },
  ,
  (
    (model: ) => ,
    
  )
)

Tabs.MenuPopper

Props

Props extend from div. Changing the as prop will change the element interface.

NameTypeDescriptionDefault
anchorElement <Element> Element null

The reference element used to position the Popper. Popper content will try to follow the anchorElement if it moves and will reposition itself if there is no longer room in the window.

children ((props: {
    placement: ;
  }) => ReactNode)
ReactNode

The content of the Popper. If a function is provided, it will be treated as a Render Prop and pass the placement chosen by PopperJS. This placement value is useful if your popup needs to animate and that animation depends on the direction of the content in relation to the anchorElement.

getAnchorClientRect() => 

When provided, this optional callback will be used to determine positioning for the Popper element instead of calling getBoundingClientRect on the anchorElement prop. Use this when you need complete control over positioning. When this prop is specified, it is safe to pass null into the anchorElement prop. If null is passed into the anchorElement prop, an owner will not be provided for the PopupStack.

openboolean

Determines if Popper content should be rendered. The content only exists in the DOM when open is true

true
placement

The placement of the Popper contents relative to the anchorElement. Accepts auto, top, right, bottom, or left. Each placement can also be modified using any of the following variations: -start or -end.

fallbackPlacements[]

Define fallback placements by providing a list of in array (in order of preference). The default preference is following the order of top, right, bottom, and left. Once the initial and opposite placements are not available, the fallback placements will be in use. Use an empty array to disable the fallback placements.

onPlacementChange(placement: ) => void

A callback function that will be called whenever PopperJS chooses a placement that is different from the provided placement preference. If a placement preference doesn't fit, PopperJS will choose a new one and call this callback.

popperOptions<PopperOptions>

The additional options passed to the Popper's popper.js instance.

portalboolean

If false, render the Popper within the DOM hierarchy of its parent. A non-portal Popper will constrained by the parent container overflows. If you set this to false, you may experience issues where you content gets cut off by scrollbars or overflow: hidden

true
popperInstanceRefRef<>

Reference to the PopperJS instance. Useful for making direct method calls on the popper instance like update.

asReact.ElementType

Optional override of the default element used by the component. Any valid tag or Component. If you provided a Component, this component should forward the ref using React.forwardRefand spread extra props to a root element.

Note: Not all elements make sense and some elements may cause accessibility issues. Change this value with care.

div
refReact.Ref<R = div>

Optional ref. If the component represents an element, this ref will be a reference to the real DOM element of the component. If as is set to an element, it will be that element. If as is a component, the reference will be to that component (or element if the component uses React.forwardRef).

model

Optional model to pass to the component. This will override the default model created for the component. This can be useful if you want to access to the state and events of the model, or if you have nested components of the same type and you need to override the model provided by React Context.

elemPropsHook(
  model: ,
  elemProps: TProps
) => HTML Attributes

Optional hook that receives the model and all props to be applied to the element. If you use this, it is your responsibility to return props, merging as appropriate. For example, returning an empty object will disable all elemProps hooks associated with this component. This allows finer control over a component without creating a new one.

usePopupPopper

Adds the necessary props to a {@link Popper } component. Used by the subcomponent.

(
  model: ,
  elemProps: {},
  ref: React.Ref
) => {
  open: boolean;
  anchorElement: <>;
  ref: (instance:  null) => void;
  onPlacementChange: (placement: ) => void;
}

Tabs.Menu

The overflow menu of the Tabs component. If there isn't enough room to render all the tab items, the extra tabs that don't fit will be overflowed into this menu.

<Tabs.Menu.Popper>
  <Tabs.Menu.Card>
    <Tabs.Menu.List>
      {(item: MyTabItem) => <Tabs.Menu.Item data-id={item.id}>
        {item.text}
      </Tabs.Menu.Item>}
    </Tabs.Menu.List>
  </Tabs.Menu.Card>
</Tabs.Menu.Popper>

This component references the component.

Model

useTabsModel

The TabsModel extends the Collection System. Tabs have tab items and panels. Tabs can be overflowed if there isn't enough room to render and will overflow to a {@link MenuModel } sub-model.

const model = useTabsModel({
  onSelect(data) {
    console.log('Selected Tab', data)
  }
})

<Tabs model={model}>{Tabs child components}</Tabs>
useTabsModel (config: ):

Specifications

Accessibility Guidelines

Keyboard Interaction

Each tab must have a focus indicator that is highly visible against the background and against the non-focused state. Refer to Accessible Colors for more information.

Tabs must support the following keyboard interactions:

  • Tab: focus the active tab element
  • Left Arrow or Right Arrow: move focus to the previous or next tab element
  • Enter and Space: activate the focused tab element
  • Alternative Tab Stop : tabpanel can optionally be included in focus order to improve discoverability for screen readers, and allow scrolling with the keyboard when the height is fixed.

Screen Reader Interaction

Tabs must communicate the following to users:

  • The focus is placed on a tab control
  • Which page tab in the set is currently selected
  • How many total number of tabs are available

Design Annotations Needed

No design annotations needed.

Implementation Markup Needed

  • Interactive elements are not allowed inside of a tablist or tab element.
  • [Included in component] All tab buttons must have a role="tab", inside of a parent element with role="tablist" attribute.
  • [Included in component] All tabs are required to have an aria-selected="false" attribute, except for the actively selected tab, must be set to “true”.
  • [Included in component] The content area container for the tablist, must have a role="tabpanel" attribute and an aria-labelledby attribute referencing the unique id of the active tab element.

Resources

Content Guidelines

  • When writing tab labels, be concise and avoid using multiple words when possible. Labels should clearly describe the contents of the tab to the user.
  • The page or page section title (if applicable) should make clear sense when placed after the tab name. Try to imagine the page title is a noun after the tab label. For example: If the page is titled “Fruit”, and the tabs are labeled “ripe” and “rotten”. You could read it in your head as “ripe fruit” or “rotten fruit”.
  • Refer to the UI Text > Tabs section of the Content Style Guide when writing tab labels.

Can't Find What You Need?

Check out our FAQ section which may help you find the information you're looking for. For further information, contact the #ask-canvas-design or #ask-canvas-kitchannels on Slack.

On this Page: