Tags Input
A component that allows users to add tags to an input field.
'use client'
import { XIcon } from 'lucide-react'
import { Button } from '~/components/ui/button'
import { IconButton } from '~/components/ui/icon-button'
import { TagsInput } from '~/components/ui/tags-input'
export const Demo = (props: TagsInput.RootProps) => {
return (
<TagsInput.Root defaultValue={['React', 'Solid', 'Vue']} maxW="xs" {...props}>
<TagsInput.Context>
{(api) => (
<>
<TagsInput.Label>Frameworks</TagsInput.Label>
<TagsInput.Control>
{api.value.map((value, index) => (
<TagsInput.Item key={index} index={index} value={value}>
<TagsInput.ItemPreview>
<TagsInput.ItemText>{value}</TagsInput.ItemText>
<TagsInput.ItemDeleteTrigger asChild>
<IconButton variant="link" size="xs">
<XIcon />
</IconButton>
</TagsInput.ItemDeleteTrigger>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
<TagsInput.HiddenInput />
</TagsInput.Item>
))}
<TagsInput.Input placeholder="Add Framework" />
</TagsInput.Control>
<TagsInput.ClearTrigger asChild>
<Button variant="outline">Clear</Button>
</TagsInput.ClearTrigger>
</>
)}
</TagsInput.Context>
</TagsInput.Root>
)
}
import { XIcon } from 'lucide-solid'
import { For } from 'solid-js'
import { Button } from '~/components/ui/button'
import { IconButton } from '~/components/ui/icon-button'
import { TagsInput } from '~/components/ui/tags-input'
export const Demo = (props: TagsInput.RootProps) => {
return (
<TagsInput.Root defaultValue={['React', 'Solid', 'Vue']} maxW="xs" {...props}>
<TagsInput.Context>
{(api) => (
<>
<TagsInput.Label>Frameworks</TagsInput.Label>
<TagsInput.Control>
<For each={api().value}>
{(value, index) => (
<TagsInput.Item index={index()} value={value}>
<TagsInput.ItemPreview>
<TagsInput.ItemText>{value}</TagsInput.ItemText>
<TagsInput.ItemDeleteTrigger
asChild={(triggerProps) => (
<IconButton variant="link" size="xs" {...triggerProps()}>
<XIcon />
</IconButton>
)}
/>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
<TagsInput.HiddenInput />
</TagsInput.Item>
)}
</For>
<TagsInput.Input placeholder="Add Framework" />
</TagsInput.Control>
<TagsInput.ClearTrigger
asChild={(triggerProps) => (
<Button variant="outline" {...triggerProps()}>
Clear
</Button>
)}
/>
</>
)}
</TagsInput.Context>
</TagsInput.Root>
)
}
Usage
import { TagsInput } from '~/components/ui/tags-input'
Installation
npx @park-ui/cli components add tags-input
1
Add Styled Primitive
Copy the code snippet below into ~/components/ui/styled/tags-input.tsx
'use client'
import type { Assign } from '@ark-ui/react'
import { TagsInput } from '@ark-ui/react/tags-input'
import { type TagsInputVariantProps, tagsInput } from 'styled-system/recipes'
import type { ComponentProps, HTMLStyledProps } from 'styled-system/types'
import { createStyleContext } from './utils/create-style-context'
const { withProvider, withContext } = createStyleContext(tagsInput)
export type RootProviderProps = ComponentProps<typeof RootProvider>
export const RootProvider = withProvider<
HTMLDivElement,
Assign<Assign<HTMLStyledProps<'div'>, TagsInput.RootProviderBaseProps>, TagsInputVariantProps>
>(TagsInput.RootProvider, 'root')
export type RootProps = ComponentProps<typeof Root>
export const Root = withProvider<
HTMLDivElement,
Assign<Assign<HTMLStyledProps<'div'>, TagsInput.RootBaseProps>, TagsInputVariantProps>
>(TagsInput.Root, 'root')
export const ClearTrigger = withContext<
HTMLButtonElement,
Assign<HTMLStyledProps<'button'>, TagsInput.ClearTriggerBaseProps>
>(TagsInput.ClearTrigger, 'clearTrigger')
export const Control = withContext<
HTMLDivElement,
Assign<HTMLStyledProps<'div'>, TagsInput.ControlBaseProps>
>(TagsInput.Control, 'control')
export const Input = withContext<
HTMLInputElement,
Assign<HTMLStyledProps<'input'>, TagsInput.InputBaseProps>
>(TagsInput.Input, 'input')
export const ItemDeleteTrigger = withContext<
HTMLButtonElement,
Assign<HTMLStyledProps<'button'>, TagsInput.ItemDeleteTriggerBaseProps>
>(TagsInput.ItemDeleteTrigger, 'itemDeleteTrigger')
export const ItemInput = withContext<
HTMLInputElement,
Assign<HTMLStyledProps<'input'>, TagsInput.ItemInputBaseProps>
>(TagsInput.ItemInput, 'itemInput')
export const ItemPreview = withContext<
HTMLDivElement,
Assign<HTMLStyledProps<'div'>, TagsInput.ItemPreviewBaseProps>
>(TagsInput.ItemPreview, 'itemPreview')
export const Item = withContext<
HTMLDivElement,
Assign<HTMLStyledProps<'div'>, TagsInput.ItemBaseProps>
>(TagsInput.Item, 'item')
export const ItemText = withContext<
HTMLSpanElement,
Assign<HTMLStyledProps<'span'>, TagsInput.ItemTextBaseProps>
>(TagsInput.ItemText, 'itemText')
export const Label = withContext<
HTMLLabelElement,
Assign<HTMLStyledProps<'label'>, TagsInput.LabelBaseProps>
>(TagsInput.Label, 'label')
export {
TagsInputContext as Context,
TagsInputHiddenInput as HiddenInput,
} from '@ark-ui/react/tags-input'
import { type Assign, TagsInput } from '@ark-ui/solid'
import type { ComponentProps } from 'solid-js'
import { type TagsInputVariantProps, tagsInput } from 'styled-system/recipes'
import type { HTMLStyledProps } from 'styled-system/types'
import { createStyleContext } from './utils/create-style-context'
const { withProvider, withContext } = createStyleContext(tagsInput)
export type RootProviderProps = ComponentProps<typeof RootProvider>
export const RootProvider = withProvider<
Assign<Assign<HTMLStyledProps<'div'>, TagsInput.RootProviderBaseProps>, TagsInputVariantProps>
>(TagsInput.RootProvider, 'root')
export type RootProps = ComponentProps<typeof Root>
export const Root = withProvider<
Assign<Assign<HTMLStyledProps<'div'>, TagsInput.RootBaseProps>, TagsInputVariantProps>
>(TagsInput.Root, 'root')
export const ClearTrigger = withContext<
Assign<HTMLStyledProps<'button'>, TagsInput.ClearTriggerBaseProps>
>(TagsInput.ClearTrigger, 'clearTrigger')
export const Control = withContext<Assign<HTMLStyledProps<'div'>, TagsInput.ControlBaseProps>>(
TagsInput.Control,
'control',
)
export const Input = withContext<Assign<HTMLStyledProps<'input'>, TagsInput.InputBaseProps>>(
TagsInput.Input,
'input',
)
export const ItemDeleteTrigger = withContext<
Assign<HTMLStyledProps<'button'>, TagsInput.ItemDeleteTriggerBaseProps>
>(TagsInput.ItemDeleteTrigger, 'itemDeleteTrigger')
export const ItemInput = withContext<
Assign<HTMLStyledProps<'input'>, TagsInput.ItemInputBaseProps>
>(TagsInput.ItemInput, 'itemInput')
export const ItemPreview = withContext<
Assign<HTMLStyledProps<'div'>, TagsInput.ItemPreviewBaseProps>
>(TagsInput.ItemPreview, 'itemPreview')
export const Item = withContext<Assign<HTMLStyledProps<'div'>, TagsInput.ItemBaseProps>>(
TagsInput.Item,
'item',
)
export const ItemText = withContext<Assign<HTMLStyledProps<'span'>, TagsInput.ItemTextBaseProps>>(
TagsInput.ItemText,
'itemText',
)
export const Label = withContext<Assign<HTMLStyledProps<'label'>, TagsInput.LabelBaseProps>>(
TagsInput.Label,
'label',
)
export {
TagsInputContext as Context,
TagsInputHiddenInput as HiddenInput,
} from '@ark-ui/solid'
No snippet found
2
Add Re-Export
To improve the developer experience, re-export the styled primitives in~/components/ui/tags-input.tsx
.
export * as TagsInput from './styled/tags-input'
export * as TagsInput from './styled/tags-input'
3
Integrate Recipe
If you're not using @park-ui/preset
, add the following recipe to yourpanda.config.ts
:
import { tagsInputAnatomy } from '@ark-ui/anatomy'
import { defineSlotRecipe } from '@pandacss/dev'
export const tagsInput = defineSlotRecipe({
className: 'tagsInput',
slots: tagsInputAnatomy.keys(),
base: {
root: {
display: 'flex',
flexDirection: 'column',
gap: '1.5',
width: 'full',
},
control: {
alignItems: 'center',
borderColor: 'border.default',
borderRadius: 'l2',
borderWidth: '1px',
display: 'flex',
flexWrap: 'wrap',
outline: 0,
transitionDuration: 'normal',
transitionProperty: 'border-color, box-shadow',
transitionTimingFunction: 'default',
_focusWithin: {
borderColor: 'colorPalette.default',
boxShadow: '0 0 0 1px var(--colors-color-palette-default)',
},
},
input: {
background: 'transparent',
color: 'fg.default',
outline: 'none',
},
itemPreview: {
alignItems: 'center',
borderColor: 'border.default',
borderRadius: 'l1',
borderWidth: '1px',
color: 'fg.default',
display: 'inline-flex',
fontWeight: 'medium',
_highlighted: {
borderColor: 'colorPalette.default',
boxShadow: '0 0 0 1px var(--colors-color-palette-default)',
},
_hidden: {
display: 'none',
},
},
itemInput: {
background: 'transparent',
color: 'fg.default',
outline: 'none',
},
label: {
color: 'fg.default',
fontWeight: 'medium',
textStyle: 'sm',
},
},
defaultVariants: {
size: 'md',
},
variants: {
size: {
md: {
root: {
gap: '1.5',
},
control: {
fontSize: 'md',
gap: '1.5',
minW: '10',
px: '3',
py: '7px', // TODO line break
},
itemPreview: {
gap: '1',
h: '6',
pe: '1',
ps: '2',
textStyle: 'sm',
},
},
},
},
})