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-input1
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 found2
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',
        },
      },
    },
  },
})