Tabs
Flexible navigation tool with various modes and features.
line
outline
enclosed
import { Tabs } from '~/components/ui/tabs'
export const Demo = (props: Tabs.RootProps) => {
const options = [
{ id: 'react', label: 'React' },
{ id: 'solid', label: 'Solid' },
{ id: 'svelte', label: 'Svelte' },
{ id: 'vue', label: 'Vue' },
]
return (
<Tabs.Root defaultValue="react" {...props}>
<Tabs.List>
{options.map((option) => (
<Tabs.Trigger key={option.id} value={option.id} disabled={option.id === 'svelte'}>
{option.label}
</Tabs.Trigger>
))}
<Tabs.Indicator />
</Tabs.List>
<Tabs.Content value="react">Know React? Check out Solid!</Tabs.Content>
<Tabs.Content value="solid">Know Solid? Check out Svelte!</Tabs.Content>
<Tabs.Content value="svelte">Know Svelte? Check out Vue!</Tabs.Content>
<Tabs.Content value="vue">Know Vue? Check out React!</Tabs.Content>
</Tabs.Root>
)
}
import { For } from 'solid-js'
import { Tabs } from '~/components/ui/tabs'
export const Demo = (props: Tabs.RootProps) => {
const options = [
{ id: 'react', label: 'React' },
{ id: 'solid', label: 'Solid' },
{ id: 'svelte', label: 'Svelte' },
{ id: 'vue', label: 'Vue' },
]
return (
<Tabs.Root defaultValue="react" {...props}>
<Tabs.List>
<For each={options}>
{(option) => (
<Tabs.Trigger value={option.id} disabled={option.id === 'svelte'}>
{option.label}
</Tabs.Trigger>
)}
</For>
<Tabs.Indicator />
</Tabs.List>
<Tabs.Content value="react">Know React? Check out Solid!</Tabs.Content>
<Tabs.Content value="solid">Know Solid? Check out Svelte!</Tabs.Content>
<Tabs.Content value="svelte">Know Svelte? Check out Vue!</Tabs.Content>
<Tabs.Content value="vue">Know Vue? Check out React!</Tabs.Content>
</Tabs.Root>
)
}
Usage
import { Tabs } from '~/components/ui/tabs'
Installation
npx @park-ui/cli components add tabs
1
Add Styled Primitive
Copy the code snippet below into ~/components/ui/styled/tabs.tsx
'use client'
import type { Assign } from '@ark-ui/react'
import { Tabs } from '@ark-ui/react/tabs'
import { type TabsVariantProps, tabs } from 'styled-system/recipes'
import type { ComponentProps, HTMLStyledProps } from 'styled-system/types'
import { createStyleContext } from './utils/create-style-context'
const { withProvider, withContext } = createStyleContext(tabs)
export type RootProviderProps = ComponentProps<typeof RootProvider>
export const RootProvider = withProvider<
HTMLDivElement,
Assign<Assign<HTMLStyledProps<'div'>, Tabs.RootProviderBaseProps>, TabsVariantProps>
>(Tabs.RootProvider, 'root')
export type RootProps = ComponentProps<typeof Root>
export const Root = withProvider<
HTMLDivElement,
Assign<Assign<HTMLStyledProps<'div'>, Tabs.RootBaseProps>, TabsVariantProps>
>(Tabs.Root, 'root')
export const Content = withContext<
HTMLDivElement,
Assign<HTMLStyledProps<'div'>, Tabs.ContentBaseProps>
>(Tabs.Content, 'content')
export const Indicator = withContext<
HTMLDivElement,
Assign<HTMLStyledProps<'div'>, Tabs.IndicatorBaseProps>
>(Tabs.Indicator, 'indicator')
export const List = withContext<HTMLDivElement, Assign<HTMLStyledProps<'div'>, Tabs.ListBaseProps>>(
Tabs.List,
'list',
)
export const Trigger = withContext<
HTMLButtonElement,
Assign<HTMLStyledProps<'button'>, Tabs.TriggerBaseProps>
>(Tabs.Trigger, 'trigger')
export { TabsContext as Context } from '@ark-ui/react/tabs'
import { type Assign, Tabs } from '@ark-ui/solid'
import type { ComponentProps } from 'solid-js'
import { type TabsVariantProps, tabs } from 'styled-system/recipes'
import type { HTMLStyledProps } from 'styled-system/types'
import { createStyleContext } from './utils/create-style-context'
const { withProvider, withContext } = createStyleContext(tabs)
export type RootProviderProps = ComponentProps<typeof RootProvider>
export const RootProvider = withProvider<
Assign<Assign<HTMLStyledProps<'div'>, Tabs.RootProviderBaseProps>, TabsVariantProps>
>(Tabs.RootProvider, 'root')
export type RootProps = ComponentProps<typeof Root>
export const Root = withProvider<
Assign<Assign<HTMLStyledProps<'div'>, Tabs.RootBaseProps>, TabsVariantProps>
>(Tabs.Root, 'root')
export const Content = withContext<Assign<HTMLStyledProps<'div'>, Tabs.ContentBaseProps>>(
Tabs.Content,
'content',
)
export const Indicator = withContext<Assign<HTMLStyledProps<'div'>, Tabs.IndicatorBaseProps>>(
Tabs.Indicator,
'indicator',
)
export const List = withContext<Assign<HTMLStyledProps<'div'>, Tabs.ListBaseProps>>(
Tabs.List,
'list',
)
export const Trigger = withContext<Assign<HTMLStyledProps<'button'>, Tabs.TriggerBaseProps>>(
Tabs.Trigger,
'trigger',
)
export { TabsContext as Context } from '@ark-ui/solid'
No snippet found
2
Add Re-Export
To improve the developer experience, re-export the styled primitives in~/components/ui/tabs.tsx
.
export * as Tabs from './styled/tabs'
export * as Tabs from './styled/tabs'
3
Integrate Recipe
If you're not using @park-ui/preset
, add the following recipe to yourpanda.config.ts
:
import { tabsAnatomy } from '@ark-ui/anatomy'
import { defineSlotRecipe } from '@pandacss/dev'
export const tabs = defineSlotRecipe({
className: 'tabs',
slots: tabsAnatomy.keys(),
base: {
root: {
display: 'flex',
width: 'full',
_horizontal: {
flexDirection: 'column',
},
_vertical: {
flexDirection: 'row',
},
},
list: {
display: 'flex',
flexShrink: '0',
_horizontal: {
flexDirection: 'row',
},
_vertical: {
flexDirection: 'column',
},
overflow: 'auto',
position: 'relative',
scrollbarWidth: 'none',
'&::-webkit-scrollbar': {
display: 'none',
},
},
trigger: {
alignItems: 'center',
color: 'fg.muted',
cursor: 'pointer',
display: 'inline-flex',
flexShrink: '0',
fontWeight: 'semibold',
gap: '2',
justifyContent: 'center',
transitionDuration: 'normal',
transitionProperty: 'color, background, border-color',
transitionTimingFunction: 'default',
whiteSpace: 'nowrap',
zIndex: '1',
_disabled: {
color: 'fg.disabled',
cursor: 'not-allowed',
_hover: {
color: 'fg.disabled',
},
},
_hover: {
color: 'fg.muted',
},
_selected: {
color: 'fg.default',
_hover: {
color: 'fg.default',
},
},
_vertical: {
justifyContent: 'flex-start',
},
},
},
defaultVariants: {
size: 'md',
variant: 'line',
},
variants: {
variant: {
enclosed: {
list: {
borderRadius: 'l3',
borderWidth: '1px',
px: '1',
backgroundColor: {
_light: 'gray.a2',
_dark: 'bg.canvas',
},
_horizontal: {
alignItems: 'center',
},
_vertical: {
height: 'fit-content!',
py: '1',
},
},
indicator: {
backgroundColor: {
_light: 'bg.default',
_dark: 'bg.subtle',
},
boxShadow: 'xs',
borderRadius: 'l2',
'--transition-duration': '200ms!',
height: 'var(--height)',
width: 'var(--width)',
},
},
line: {
list: {
_horizontal: {
boxShadow: '0 -1px 0 0 inset var(--colors-border-default)',
gap: '4',
},
_vertical: {
boxShadow: '1px 0 0 0 inset var(--colors-border-default)',
gap: '1',
},
},
indicator: {
background: 'colorPalette.default',
_horizontal: {
bottom: '0',
height: '2px',
width: 'var(--width)',
},
_vertical: {
height: 'var(--height)',
left: '0',
width: '2px',
},
},
content: {
pt: '4',
},
trigger: {
_horizontal: {
pb: '2.5',
},
},
},
outline: {
list: {
_horizontal: {
mb: '-1px',
},
_vertical: {
mr: '-1px',
},
},
trigger: {
borderColor: 'transparent',
borderWidth: '1px',
_horizontal: {
borderTopRadius: 'l2',
},
_vertical: {
borderTopLeftRadius: 'l2',
borderBottomLeftRadius: 'l2',
},
_selected: {
background: 'bg.default',
borderColor: 'border.subtle',
_horizontal: {
borderBottomColor: 'transparent',
},
_vertical: {
borderRightColor: 'transparent',
},
},
},
content: {
borderWidth: '1px',
borderColor: 'border.subtle',
background: 'bg.default',
width: 'full',
},
},
},
size: {
sm: {
trigger: {
'& svg': {
width: '4',
height: '4',
},
},
},
md: {
trigger: {
'& svg': {
width: '5',
height: '5',
},
},
},
lg: {
trigger: {
'& svg': {
width: '5',
height: '5',
},
},
},
},
},
compoundVariants: [
{
size: 'sm',
variant: 'enclosed',
css: {
list: {
height: '10',
},
trigger: {
h: '8',
minW: '8',
textStyle: 'sm',
px: '3',
},
content: {
p: '3.5',
},
},
},
{
size: 'md',
variant: 'enclosed',
css: {
list: {
height: '11',
},
trigger: {
h: '9',
minW: '9',
textStyle: 'sm',
px: '3.5',
},
content: {
p: '4',
},
},
},
{
size: 'lg',
variant: 'enclosed',
css: {
list: {
height: '12',
},
trigger: {
h: '10',
minW: '10',
textStyle: 'sm',
px: '4',
},
content: {
p: '4.5',
},
},
},
{
size: 'sm',
variant: 'outline',
css: {
trigger: {
h: '9',
minW: '9',
textStyle: 'sm',
px: '3.5',
},
content: {
p: '3.5',
},
},
},
{
size: 'md',
variant: 'outline',
css: {
trigger: {
h: '10',
minW: '10',
textStyle: 'sm',
px: '4',
},
content: {
p: '4',
},
},
},
{
size: 'lg',
variant: 'outline',
css: {
trigger: {
h: '11',
minW: '11',
textStyle: 'md',
px: '4.5',
},
content: {
p: '4.5',
},
},
},
{
size: 'sm',
variant: 'line',
css: {
trigger: {
fontSize: 'sm',
h: '9',
minW: '9',
px: '2.5',
},
content: {
pt: '3',
},
},
},
{
size: 'md',
variant: 'line',
css: {
trigger: {
fontSize: 'md',
h: '10',
minW: '10',
px: '3',
},
content: {
pt: '4',
},
},
},
{
size: 'lg',
variant: 'line',
css: {
trigger: {
px: '3.5',
h: '11',
minW: '11',
fontSize: 'md',
},
content: {
pt: '5',
},
},
},
],
})