Components
Tags input

Tags Input

A component that allows users to add tags to an input field.

Usage

React
Solid
Vue
import { XIcon } from 'lucide-react'
import { Button } from '~/components/ui/button'
import { IconButton } from '~/components/ui/icon-button'
import * as TagsInput from '~/components/ui/tags-input'

export const Demo = (props: TagsInput.RootProps) => {
  return (
    <TagsInput.Root defaultValue={['React', 'Solid', 'Vue']} maxW="xs" {...props}>
      {(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.Item>
            ))}
            <TagsInput.Input placeholder="Add Framework" />
          </TagsInput.Control>
          <TagsInput.ClearTrigger asChild>
            <Button variant="outline">Clear</Button>
          </TagsInput.ClearTrigger>
        </>
      )}
    </TagsInput.Root>
  )
}

Installation

1

Add Component

Insert code snippet into your project. Update import paths as needed.

import { TagsInput } from '@ark-ui/react/tags-input'
import type { ComponentProps } from 'react'
import { styled } from 'styled-system/jsx'
import { tagsInput } from 'styled-system/recipes'
import { createStyleContext } from '~/lib/create-style-context'

const { withProvider, withContext } = createStyleContext(tagsInput)

export const Root = withProvider(styled(TagsInput.Root), 'root')
export const ClearTrigger = withContext(styled(TagsInput.ClearTrigger), 'clearTrigger')
export const Control = withContext(styled(TagsInput.Control), 'control')
export const Input = withContext(styled(TagsInput.Input), 'input')
export const Item = withContext(styled(TagsInput.Item), 'item')
export const ItemDeleteTrigger = withContext(
  styled(TagsInput.ItemDeleteTrigger),
  'itemDeleteTrigger',
)
export const ItemInput = withContext(styled(TagsInput.ItemInput), 'itemInput')
export const ItemPreview = withContext(styled(TagsInput.ItemPreview), 'itemPreview')
export const ItemText = withContext(styled(TagsInput.ItemText), 'itemText')
export const Label = withContext(styled(TagsInput.Label), 'label')

export interface RootProps extends ComponentProps<typeof Root> {}
export interface ClearTriggerProps extends ComponentProps<typeof ClearTrigger> {}
export interface ControlProps extends ComponentProps<typeof Control> {}
export interface InputProps extends ComponentProps<typeof Input> {}
export interface ItemProps extends ComponentProps<typeof Item> {}
export interface ItemDeleteTriggerProps extends ComponentProps<typeof ItemDeleteTrigger> {}
export interface ItemInputProps extends ComponentProps<typeof ItemInput> {}
export interface ItemPreviewProps extends ComponentProps<typeof ItemPreview> {}
export interface ItemTextProps extends ComponentProps<typeof ItemText> {}
export interface LabelProps extends ComponentProps<typeof Label> {}
2

Add Recipe

This step is necessary only if you do not use any of the Park UI plugins.

import { tagsInputAnatomy } from '@ark-ui/anatomy'
import { defineSlotRecipe } from '@pandacss/dev'

export const tagsInput = defineSlotRecipe({
  className: 'tagsInput',
  slots: tagsInputAnatomy.keys(),
  base: {
    root: {
      colorPalette: 'accent',
      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',
        },
      },
    },
  },
})

Previous

Tabs

On this page