Tabs Primitive
An unstyled and accessible basis upon which to style a tabset.
const HorizontalExample = () => {const tab = useTabPrimitiveState();return (<><TabPrimitiveList {...tab} aria-label="My tabs"><TabPrimitive {...tab}>Tab 1</TabPrimitive><TabPrimitive {...tab} disabled>Tab 2 (disabled)</TabPrimitive><TabPrimitive {...tab}>Tab 3</TabPrimitive></TabPrimitiveList><TabPrimitivePanel {...tab}>Tab 1</TabPrimitivePanel><TabPrimitivePanel {...tab}>Tab 2</TabPrimitivePanel><TabPrimitivePanel {...tab}>Tab 3</TabPrimitivePanel></>)};render(<HorizontalExample />)
The Tabs primitive is an unstyled, functional version of a tabset component. It can be used to build a component following the WAI-ARIA Tabs Pattern. Our Tabs component is built on top of this primitive.
This unstyled primitive is to be used when the styled Tabs provided by Paste doesn’t meet the requirements needed to solve a unique customer problem. At that point, you are welcome to fall back to this functional primitive to roll your own styled Tabs while still providing a functional and accessible experience to your customers.
This primitive should be used to compose all custom Tabs to ensure accessibility and upgrade paths.
Before you roll your own tabset...
We strongly suggest that all components built on top of this primitive get reviewed by the Design Systems team and go through the UX Review process to ensure an excellent experience for our customers.
const HorizontalExample = () => {const tab = useTabPrimitiveState();return (<><TabPrimitiveList {...tab} aria-label="My tabs"><TabPrimitive {...tab}>Tab 1</TabPrimitive><TabPrimitive {...tab} disabled>Tab 2 (disabled)</TabPrimitive><TabPrimitive {...tab}>Tab 3</TabPrimitive></TabPrimitiveList><TabPrimitivePanel {...tab}>Tab 1</TabPrimitivePanel><TabPrimitivePanel {...tab}>Tab 2</TabPrimitivePanel><TabPrimitivePanel {...tab}>Tab 3</TabPrimitivePanel></>)};render(<HorizontalExample />)
const VerticalExample = () => {const tab = useTabPrimitiveState({orientation: 'vertical'});return (<Stack orientation="horizontal" spacing="space40"><Box><TabPrimitiveList {...tab} aria-label="My tabs"><Stack orientation="vertical" spacing="space20"><TabPrimitive {...tab}>Tab 1</TabPrimitive><TabPrimitive {...tab}>Tab 2</TabPrimitive><TabPrimitive {...tab}>Tab 3</TabPrimitive></Stack></TabPrimitiveList></Box><Box><TabPrimitivePanel {...tab}>Tab 1</TabPrimitivePanel><TabPrimitivePanel {...tab}>Tab 2</TabPrimitivePanel><TabPrimitivePanel {...tab}>Tab 3</TabPrimitivePanel></Box></Stack>)};render(<VerticalExample />)
The tab primitive can be styled using Paste components and tokens. By using the as
prop, we can
change the underlying element and combine it with another component. In the example below we're combining
the TabPrimitive
with a custom Box
component. We are rendering the TabPrimitive
as CustomTab
, passing down
the necessary props from the state hook and combining those with Box
styling props.
const CustomTab = React.forwardRef((props, ref) => (<Boxas="div"ref={ref}borderBottomColor="transparent"borderRadius="borderRadius0"borderBottomStyle="solid"borderBottomWidth="borderWidth20"cursor="pointer"padding="space20"outline="none"_selected={{borderColor:"colorBorderPrimary"}}_focus={{textDecoration: 'underline'}}_disabled={{color: 'colorTextWeaker'}}{...props}/>));const StyledExample = () => {const tab = useTabPrimitiveState();return (<><TabPrimitiveList {...tab} aria-label="My tabs"><Stack orientation="horizontal" spacing="space20"><TabPrimitive {...tab} as={CustomTab}>Tab 1</TabPrimitive><TabPrimitive {...tab} disabled as={CustomTab}>Tab 2</TabPrimitive><TabPrimitive {...tab} as={CustomTab}>Tab 3</TabPrimitive></Stack></TabPrimitiveList><TabPrimitivePanel {...tab}>Tab 1</TabPrimitivePanel><TabPrimitivePanel {...tab}>Tab 2</TabPrimitivePanel><TabPrimitivePanel {...tab}>Tab 3</TabPrimitivePanel></>)};render(<StyledExample />)
This package is a wrapper around the Reakit Tab. If you’re wondering why we wrapped that package into our own, we reasoned that it would be best for our consumers’ developer experience. For example:
- By wrapping a library in
@twilio-paste/x-primitive
, any update or swap of the underlying library would only be a version bump in thepackage.json
file for the primitive. Without this, if we want to migrate the underlying library in the future, Twilio products that depend on this primitive would need to replace all occurrences ofimport … from 'x-package'
toimport … from '@some-new/package'
. - We can more strictly enforce semver and backwards compatibility than some of our dependencies.
- We can control when to provide an update and which versions we allow, to help reduce potential bugs our consumers may face.
- We can control which APIs we expose. For example, we may chose to enable or disable usage of certain undocumented APIs.
yarn add @twilio-paste/tabs-primitive - or - yarn add @twilio-paste/core
import {
useTabPrimitiveState,
TabPrimitiveList,
TabPrimitive,
TabPrimitivePanel,
} from '@twilio-paste/core/tabs-primitive';
const HorizontalExample = () => {
const tab = useTabPrimitiveState();
return (
<>
<TabPrimitiveList {...tab} aria-label="My tabs">
<TabPrimitive {...tab}>Tab 1</TabPrimitive>
<TabPrimitive {...tab} disabled>
Tab 2 (disabled)
</TabPrimitive>
<TabPrimitive {...tab}>Tab 3</TabPrimitive>
</TabPrimitiveList>
<TabPrimitivePanel {...tab}>Tab 1</TabPrimitivePanel>
<TabPrimitivePanel {...tab}>Tab 2</TabPrimitivePanel>
<TabPrimitivePanel {...tab}>Tab 3</TabPrimitivePanel>
</>
);
};
This props list is a scoped version of the properties available from the Reakit Tab package.
baseId string
ID that will serve as a base for all the items IDs.
rtl boolean
Determines how next
and previous
functions will behave. If rtl
is
set to true
, they will be inverted. You still need to set dir="rtl"
on
HTML/CSS.
orientation "horizontal" | "vertical" | undefined
Defines the orientation of the composite widget. If the composite has a
single row or column (one-dimensional), the orientation
value determines
which arrow keys can be used to move focus:
It doesn't have any effect on two-dimensional composites.
currentId string | null | undefined
The current focused item id
.
loop boolean | "horizontal" | "vertical"
Whether keyboard navigation loops back to the beginning.
wrap boolean | "horizontal" | "vertical"
If enabled, moving to the next item from the last one in a row or column will focus the first item in the next row or column and vice-versa.
selectedId string | null | undefined
The current selected tab's id
.
manual boolean
Whether the tab selection should be manual.
disabled boolean | undefined
Same as the HTML attribute.
focusable boolean | undefined
When an element is disabled
, it may still be focusable
. It works
similarly to readOnly
on form elements. In this case, only
aria-disabled
will be set.
id string | undefined
Same as the HTML attribute.
State props
These props are returned by the state hook. You can spread them into this component (...state) or pass them separately. You can also provide these props from your own state logic.
baseId string
ID that will serve as a base for all the items IDs.
orientation "horizontal" | "vertical" | undefined
Defines the orientation of the composite widget. If the composite has a
single row or column (one-dimensional), the orientation
value determines
which arrow keys can be used to move focus:
It doesn't have any effect on two-dimensional composites.
currentId string | null | undefined
The current focused item id
.
items Item[]
Lists all the composite items with their id
, DOM ref
, disabled
state
and groupId
if any. This state is automatically updated when
registerItem
and unregisterItem
are called.
setCurrentId (value: SetStateAction<string | null | undefined>) => void
Sets currentId
.
registerItem (item: Item) => void
Registers a composite item.
unregisterItem (id: string) => void
Unregisters a composite item.
next (unstable_allTheWay?: boolean | undefined) => void
Moves focus to the next item.
previous (unstable_allTheWay?: boolean | undefined) => void
Moves focus to the previous item.
up (unstable_allTheWay?: boolean | undefined) => void
Moves focus to the item above.
down (unstable_allTheWay?: boolean | undefined) => void
Moves focus to the item below.
first () => void
Moves focus to the first item.
last () => void
Moves focus to the last item.
manual boolean
Whether the tab selection should be manual.
selectedId string | null | undefined
The current selected tab's id
.
panels Item[]
Lists all the panels.
select (id: string | null) => void
Moves into and selects a tab by its id
.
disabled boolean | undefined
Same as the HTML attribute.
focusable boolean | undefined
When an element is disabled
, it may still be focusable
. It works
similarly to readOnly
on form elements. In this case, only
aria-disabled
will be set.
State props
These props are returned by the state hook. You can spread them into this component (...state) or pass them separately. You can also provide these props from your own state logic.
baseId string
ID that will serve as a base for all the items IDs.
orientation "horizontal" | "vertical" | undefined
Defines the orientation of the composite widget. If the composite has a
single row or column (one-dimensional), the orientation
value determines
which arrow keys can be used to move focus:
currentId string | null | undefined
The current focused item id
.
wrap boolean | "horizontal" | "vertical"
If enabled, moving to the next item from the last one in a row or column will focus the first item in the next row or column and vice-versa.
groups Group[]
Lists all the composite groups with their id
and DOM ref
. This state
is automatically updated when registerGroup
and unregisterGroup
are
called.
items Item[]
Lists all the composite items with their id
, DOM ref
, disabled
state
and groupId
if any. This state is automatically updated when
registerItem
and unregisterItem
are called.
move (id: string | null) => void
Moves focus to a given item ID.
setCurrentId (value: SetStateAction<string | null | undefined>) => void
Sets currentId
.
first () => void
Moves focus to the first item.
last () => void
Moves focus to the last item.
id string | undefined
Same as the HTML attribute.
tabId string | undefined
Tab's id
State props
These props are returned by the state hook. You can spread them into this component (...state) or pass them separately. You can also provide these props from your own state logic.
baseId string
ID that will serve as a base for all the items IDs.
visible boolean
Whether it's visible or not.
animating boolean
Whether it's animating or not.
animated number | boolean
If true
, animating
will be set to true
when visible
is updated.
It'll wait for stopAnimation
to be called or a CSS transition ends.
If animated
is set to a number
, stopAnimation
will be called only
after the same number of milliseconds have passed.
stopAnimation () => void
Stops animation. It's called automatically if there's a CSS transition.
selectedId string | null | undefined
The current selected tab's id
.
items Item[]
Lists all the composite items with their id
, DOM ref
, disabled
state
and groupId
if any. This state is automatically updated when
registerItem
and unregisterItem
are called.
panels Item[]
Lists all the panels.
registerPanel (item: Item) => void
Registers a tab panel.
unregisterPanel (id: string) => void
Unregisters a tab panel.
Changelog
b97c0525b
#3607 Thanks @SiTaggart! - [Tabs primitive] Updated dev depenedencies to include typescript and tsx for running build scripts
733709127
#3395 Thanks @SiTaggart! - Modified the compile target of our JavaScript bundles fromnode
tobrowser
to minimize the risk of clashing with RequireJS. This is marked as a major out of an abundance of caution. You shouldn't need to do anything but we wanted you to be aware of the change on the off chance it has unintended consequences
- Updated dependencies [
733709127
]:- @twilio-paste/reakit-library@2.0.0
- Updated dependencies [
3c89fd83d
]:- @twilio-paste/reakit-library@1.0.0
a4c9e70b0
#2763 Thanks @shleewhite! - Update ESLint rules, which changed some formatting.
ae9dd50f
#2466 Thanks @TheSisb! - [All packages] Update our ESBuild version and remove minification of identifiers in our production builds.
12a5e83e
#2449 Thanks @shleewhite! - Made a slight improvement to the TypeScript typings of several packages for better interoperability.
73c596919
#2269 Thanks @SiTaggart! - Fixed a regression with the compilation script that caused incompatible ESM module importing of JSON files.
c867e3f48
#2237 Thanks @SiTaggart! - Updated a build dependency (esbuild) which changes the output of our builds slightly, without materially changing anything about the code.
b7675915
#1985 Thanks @TheSisb! - For debugging purposes we now ship afilename.debug.js
unminified version of each component or library in Paste.
ed5c0a49c
#1965 Thanks @shleewhite! - Upgrade Paste to use React 17 by default, but maintain React 16 support for consumers.
0eded1fd
#1319 Thanks @SiTaggart! - Change internal dependencies to have minor range matching on version numbers
a12acb61
#1158 Thanks @richbachman! - Pinned all twilio-paste package versions in order to keep them in sync with core when they are updated by changesets.
All notable changes to this project will be documented in this file. See Conventional Commits for commit guidelines.
0.2.1 (2021-01-25)
Note: Version bump only for package @twilio-paste/tabs-primitive
0.1.14 (2021-01-15)
Note: Version bump only for package @twilio-paste/tabs-primitive
0.1.13 (2021-01-14)
Note: Version bump only for package @twilio-paste/tabs-primitive
0.1.12 (2020-10-27)
Note: Version bump only for package @twilio-paste/tabs-primitive
0.1.11 (2020-10-23)
Note: Version bump only for package @twilio-paste/tabs-primitive
0.1.10 (2020-10-13)
Note: Version bump only for package @twilio-paste/tabs-primitive
0.1.9 (2020-09-22)
Note: Version bump only for package @twilio-paste/tabs-primitive
0.1.8 (2020-09-15)
Note: Version bump only for package @twilio-paste/tabs-primitive
0.1.7 (2020-09-03)
Note: Version bump only for package @twilio-paste/tabs-primitive
0.1.6 (2020-07-01)
Note: Version bump only for package @twilio-paste/tabs-primitive
0.1.5 (2020-07-01)
Note: Version bump only for package @twilio-paste/tabs-primitive
0.1.4 (2020-06-25)
Note: Version bump only for package @twilio-paste/tabs-primitive
0.1.3 (2020-06-16)
Note: Version bump only for package @twilio-paste/tabs-primitive
0.1.2 (2020-06-10)
- tabs-primitive: rename export to singular tab to match usage (f0bd282)
0.1.1 (2020-06-09)
Note: Version bump only for package @twilio-paste/tabs-primitive