Installation
npx @park-ui/cli add segment-groupAdd Component
Copy the code snippet below into you components folder.
'use client'
import { SegmentGroup } from '@ark-ui/react/segment-group'
import { type ComponentProps, type ReactNode, useMemo } from 'react'
import { createStyleContext } from 'styled-system/jsx'
import { segmentGroup } from 'styled-system/recipes'
const { withProvider, withContext } = createStyleContext(segmentGroup)
export type RootProps = ComponentProps<typeof Root>
export const Root = withProvider(SegmentGroup.Root, 'root', {
defaultProps: { orientation: 'horizontal' },
forwardProps: ['orientation'],
})
export const RootProvider = withProvider(SegmentGroup.RootProvider, 'root')
export const Indicator = withContext(SegmentGroup.Indicator, 'indicator')
export const Item = withContext(SegmentGroup.Item, 'item')
export const ItemControl = withContext(SegmentGroup.ItemControl, 'itemControl')
export const ItemHiddenInput = SegmentGroup.ItemHiddenInput
export const ItemText = withContext(SegmentGroup.ItemText, 'itemText')
export const Label = withContext(SegmentGroup.Label, 'label')
export { SegmentGroupContext as Context } from '@ark-ui/react/segment-group'
interface Item {
value: string
label: ReactNode
disabled?: boolean | undefined
}
type ItemProps = ComponentProps<typeof Item>
export interface ItemsProps extends Omit<ItemProps, 'value'> {
items: Array<string | Item>
}
export const Items = (props: ItemsProps) => {
const { items, ...itemProps } = props
const data = useMemo(() => normalize(items), [items])
return data.map((item) => (
<Item key={item.value} value={item.value} disabled={item.disabled} {...itemProps}>
<ItemText>{item.label}</ItemText>
<ItemHiddenInput />
</Item>
))
}
const normalize = (items: Array<string | Item>): Item[] =>
items.map((item) => (typeof item === 'string' ? { value: item, label: item } : item))
Integrate Recipe
Integrate this recipe in to your Panda config.
import { segmentGroupAnatomy } from '@ark-ui/react/segment-group'
import { defineSlotRecipe } from '@pandacss/dev'
export const segmentGroup = defineSlotRecipe({
className: 'segment-group',
slots: segmentGroupAnatomy.keys(),
base: {
root: {
bg: {
_light: 'gray.2',
_dark: 'gray.1',
},
borderRadius: 'l3',
boxShadow: 'inset 0 0 0px 1px var(--shadow-color)',
boxShadowColor: 'border',
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
isolation: 'isolate',
pos: 'relative',
_vertical: {
flexDirection: 'column',
alignItems: 'stretch',
},
},
item: {
alignItems: 'center',
borderRadius: 'l3',
display: 'inline-flex',
flexShrink: '0',
fontWeight: 'medium',
gap: '2',
justifyContent: 'center',
position: 'relative',
userSelect: 'none',
_disabled: {
opacity: '0.5',
},
'&:has(input:focus-visible)': {
focusVisibleRing: 'outside',
},
_before: {
content: '""',
position: 'absolute',
bg: 'gray.surface.border',
transition: 'opacity 0.2s',
},
_horizontal: {
_before: {
insetInlineStart: '0',
insetBlock: '1.5',
width: '1px',
},
},
_vertical: {
_before: {
insetBlockStart: '0',
insetInline: '1.5',
height: '1px',
},
},
'& + &[data-state=checked], &[data-state=checked] + &, &:first-of-type': {
_before: {
opacity: '0',
},
},
},
indicator: {
bg: {
_light: 'white',
_dark: 'gray.2',
},
borderWidth: '1px',
borderColor: 'gray.surface.border',
borderRadius: 'l3',
height: 'var(--height)',
pos: 'absolute',
width: 'var(--width)',
zIndex: -1,
},
},
variants: {
size: {
xs: { item: { h: '8', minW: '8', textStyle: 'sm', px: '2.5', _icon: { boxSize: '4' } } },
sm: { item: { h: '9', minW: '9', textStyle: 'sm', px: '3', _icon: { boxSize: '4' } } },
md: { item: { h: '10', minW: '10', textStyle: 'sm', px: '3.5', _icon: { boxSize: '5' } } },
lg: { item: { h: '11', minW: '11', textStyle: 'md', px: '4', _icon: { boxSize: '5' } } },
xl: { item: { h: '12', minW: '12', textStyle: 'md', px: '4.5', _icon: { boxSize: '5.5' } } },
},
fitted: {
true: {
root: {
display: 'flex',
},
item: {
flex: '1',
},
},
},
},
defaultVariants: {
size: 'md',
},
})
Usage
import { SegmentGroup } from '@/components/ui'
<SegmentGroup.Root>
<SegmentGroup.Indicator />
<SegmentGroup.Item>
<SegmentGroup.ItemText />
<SegmentGroup.ItemHiddenInput />
</SegmentGroup.Item>
</SegmentGroup.Root>
Shortcuts
The SegmentGroup component also provides a set of shortcuts for common use cases.
SegmentGroupItems
The SegmentGroup.Items shortcut renders a list of items based on the items prop:
<>
{items.map((item) => (
<SegmentGroup.Item key={item.value} value={item.value}>
<SegmentGroup.ItemText>{item.label}</SegmentGroup.ItemText>
<SegmentGroup.ItemHiddenInput />
</SegmentGroup.Item>
))}
</>
This might be more concise, if you don't need to customize the items:
<SegmentGroup.Items items={items} />
Examples
Sizes
Use the size prop to change the size of the segmented group.
Fitted
Use the fitted prop to make the tabs fit the width of the container.
Vertical
By default, the segmented control is horizontal. Set the orientation prop to vertical to change the orientation of the segmented group.
Icon
Use icons inside the segmented group items.
Props
Root
| Prop | Default | Type |
|---|---|---|
size | 'md' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' |
fitted | boolean | |
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. | |
defaultValue | stringThe initial value of the checked radio when rendered. Use when you don't need to control the value of the radio group. | |
disabled | booleanIf `true`, the radio group will be disabled | |
form | stringThe associate form of the underlying input. | |
id | stringThe unique identifier of the machine. | |
ids | Partial<{
root: string
label: string
indicator: string
item: (value: string) => string
itemLabel: (value: string) => string
itemControl: (value: string) => string
itemHiddenInput: (value: string) => string
}>The ids of the elements in the radio. Useful for composition. | |
name | stringThe name of the input fields in the radio (Useful for form submission). | |
onValueChange | (details: ValueChangeDetails) => voidFunction called once a radio is checked | |
orientation | 'horizontal' | 'vertical'Orientation of the radio group | |
readOnly | booleanWhether the checkbox is read-only | |
value | stringThe controlled value of the radio group |
Item
| Prop | Default | Type |
|---|---|---|
value* | string | |
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. | |
disabled | boolean | |
invalid | boolean |