All components
Tags Input
Enter-to-add token input with removable item previews in outline, subtle, and flushed styles.
123456789101112131415161718192021222324252627import { TagsInput } from "#components/ui/ark-ui/tags-input.tsx";
export function Example() { return ( // variant/size resolve once on Root; every part inherits them via // context — ItemDeleteTrigger ships a default close icon <TagsInput.Root defaultValue={["Design", "Engineering"]} variant="outline" size="md"> <TagsInput.Label>Teams</TagsInput.Label> <TagsInput.Control> <TagsInput.Context> {(api) => api.value.map((tag, index) => ( <TagsInput.Item key={index} index={index} value={tag}> <TagsInput.ItemPreview> <TagsInput.ItemText>{tag}</TagsInput.ItemText> <TagsInput.ItemDeleteTrigger /> </TagsInput.ItemPreview> </TagsInput.Item> )) } </TagsInput.Context> <TagsInput.Input placeholder="Add team" /> </TagsInput.Control> <TagsInput.HiddenInput /> </TagsInput.Root> );}Variants
outline
subtle
flushed
Sizes
xs
sm
md
lg
Install
1npx stylexblocks add tags-inputSource
components/ui/ark-ui/tags-input.tsx
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406import * as stylex from "@stylexjs/stylex";import { TagsInput as ArkTagsInput } from "@ark-ui/react/tags-input";import { createSlotRecipeContext } from "#lib/ark-ui/create-slot-recipe-context.tsx";import { defineSlotRecipe, resolveSlotStyles, type StyleOverrideProps } from "#lib/recipe-props.ts";import { tagsInputItemDeleteTriggerIconVars, tagsInputClearTriggerIconVars,} from '@stylexblocks/ui/slot-recipes/tags-input.stylex.ts'
import { colorPalette } from '@stylexblocks/ui/color-palette.stylex.ts'import { durations } from '@stylexblocks/ui/tokens/durations.stylex.ts'import { fontSizes } from '@stylexblocks/ui/tokens/font-sizes.stylex.ts'import { fontWeights } from '@stylexblocks/ui/tokens/font-weights.stylex.ts'import { lineHeights } from '@stylexblocks/ui/tokens/line-heights.stylex.ts'import { semanticColors } from '@stylexblocks/ui/semantic-tokens/colors.stylex.ts'import { semanticRadii } from '@stylexblocks/ui/semantic-tokens/radii.stylex.ts'import { sizes } from '@stylexblocks/ui/tokens/sizes.stylex.ts'import { spacing } from '@stylexblocks/ui/tokens/spacing.stylex.ts'
const tagsInputRoot = stylex.create({ base: { display: 'flex', flexDirection: 'column', gap: spacing._1_5, width: sizes.full, },})
const tagsInputRootSizes = stylex.create({ xs: { '--tags-input-height': sizes._8, '--tags-input-px': spacing._1_5, '--tags-input-py': spacing._1, '--tags-input-gap': spacing._1, '--tags-input-item-height': sizes._6, '--tags-input-item-px': spacing._2, fontSize: fontSizes.xs, lineHeight: lineHeights.xs, }, sm: { '--tags-input-height': sizes._9, '--tags-input-px': spacing._1_5, '--tags-input-py': spacing._1, '--tags-input-gap': spacing._1, '--tags-input-item-height': sizes._6, '--tags-input-item-px': spacing._2, fontSize: fontSizes.sm, lineHeight: lineHeights.sm, }, md: { '--tags-input-height': sizes._10, '--tags-input-px': spacing._1_5, '--tags-input-py': spacing._1, '--tags-input-gap': spacing._1, '--tags-input-item-height': sizes._7, '--tags-input-item-px': spacing._2, fontSize: fontSizes.sm, lineHeight: lineHeights.sm, }, lg: { '--tags-input-height': sizes._10, '--tags-input-px': spacing._1_5, '--tags-input-py': spacing._1, '--tags-input-gap': spacing._1, '--tags-input-item-height': sizes._8, '--tags-input-item-px': spacing._2, fontSize: fontSizes.md, lineHeight: lineHeights.md, },})
const tagsInputLabel = stylex.create({ base: { fontWeight: fontWeights.medium, fontSize: fontSizes.sm, lineHeight: lineHeights.sm, opacity: { default: null, ':is(:disabled, [disabled], [data-disabled])': 0.5, }, },})
const tagsInputControl = stylex.create({ base: { '--focus-color': colorPalette.focusRing, '--error-color': semanticColors.borderError, minHeight: 'var(--tags-input-height)', '--input-height': 'var(--tags-input-height)', paddingInline: 'var(--tags-input-px)', paddingBlock: 'var(--tags-input-py)', gap: 'var(--tags-input-gap)', display: 'flex', flexWrap: 'wrap', alignItems: 'center', borderRadius: semanticRadii.l2, position: 'relative', transitionProperty: 'border-color, box-shadow', transitionDuration: durations.moderate, opacity: { default: null, ':is(:disabled, [disabled], [data-disabled])': 0.5, }, borderColor: { default: null, ':is([data-invalid], [aria-invalid="true"])': 'var(--error-color)', }, },})
const tagsInputControlVariants = stylex.create({ outline: { borderWidth: '1px', backgroundColor: semanticColors.bg, outlineWidth: { default: null, ':focus': '1px', }, outlineStyle: { default: null, ':focus': 'solid', }, outlineColor: { default: null, ':focus': { default: 'var(--focus-color)', ':is([data-invalid], [aria-invalid="true"])': 'var(--error-color)', }, }, borderColor: { default: null, ':is([data-invalid], [aria-invalid="true"])': 'var(--error-color)', ':focus': { default: 'var(--focus-color)', ':is([data-invalid], [aria-invalid="true"])': 'var(--error-color)', }, }, }, subtle: { backgroundColor: semanticColors.bgMuted, borderWidth: '1px', borderColor: { default: 'transparent', ':is([data-invalid], [aria-invalid="true"])': 'var(--error-color)', ':focus': { default: 'var(--focus-color)', ':is([data-invalid], [aria-invalid="true"])': 'var(--error-color)', }, }, outlineWidth: { default: null, ':focus': '1px', }, outlineStyle: { default: null, ':focus': 'solid', }, outlineColor: { default: null, ':focus': { default: 'var(--focus-color)', ':is([data-invalid], [aria-invalid="true"])': 'var(--error-color)', }, }, }, flushed: { borderRadius: 0, paddingInline: 0, backgroundColor: 'transparent', borderBottomWidth: '1px', borderBottomColor: semanticColors.border, borderColor: { default: null, ':is([data-invalid], [aria-invalid="true"])': 'var(--error-color)', ':focus': 'var(--focus-color)', }, boxShadow: { default: null, ':focus': '0px 1px 0px 0px var(--focus-color)', }, },})
const tagsInputInput = stylex.create({ base: { flex: 1, minWidth: sizes._20, outline: 'none', backgroundColor: 'transparent', color: semanticColors.fg, paddingInline: 'calc(var(--tags-input-item-px) / 1.25)', height: 'var(--tags-input-item-height)', display: { default: null, ':is([data-readonly], [aria-readonly="true"], [readonly])': 'none', }, },})
const tagsInputItemText = stylex.create({ base: { lineClamp: 1, },})
const tagsInputItemInput = stylex.create({ base: { outline: 'none', backgroundColor: 'transparent', minWidth: '2ch', color: 'inherit', paddingInline: 'var(--tags-input-item-px)', height: 'var(--tags-input-item-height)', },})
const tagsInputItemPreview = stylex.create({ base: { height: 'var(--tags-input-item-height)', userSelect: 'none', display: 'inline-flex', alignItems: 'center', gap: spacing._1, borderRadius: semanticRadii.l1, paddingInline: 'var(--tags-input-item-px)', },})
const tagsInputItemPreviewVariants = stylex.create({ outline: { backgroundColor: colorPalette.subtle, }, subtle: { backgroundColor: semanticColors.bg, borderWidth: '1px', }, flushed: { backgroundColor: colorPalette.subtle, },})
const tagsInputItemDeleteTrigger = stylex.create({ base: { display: 'flex', alignItems: 'center', justifyContent: 'center', width: 'calc(var(--tags-input-item-height) / 1.5)', height: 'calc(var(--tags-input-item-height) / 1.5)', marginInlineEnd: `calc(${spacing._1} * -1)`, opacity: { default: 0.4, ':hover': 1, }, [tagsInputItemDeleteTriggerIconVars.width]: '80%', [tagsInputItemDeleteTriggerIconVars.height]: '80%', },})
const tagsInputItemDeleteTriggerIcon = stylex.create({ base: { width: tagsInputItemDeleteTriggerIconVars.width, height: tagsInputItemDeleteTriggerIconVars.height, },})
const tagsInputClearTrigger = stylex.create({ base: { display: 'flex', alignItems: 'center', justifyContent: 'center', width: 'calc(var(--tags-input-item-height) / 1.5)', height: 'calc(var(--tags-input-item-height) / 1.5)', color: semanticColors.fgMuted, outlineWidth: { default: null, ':focus-visible': 'var(--focus-ring-width, 0)', }, outlineOffset: { default: null, ':focus-visible': '0px', }, outlineStyle: { default: null, ':focus-visible': 'solid', }, outlineColor: { default: null, ':focus-visible': colorPalette.focusRing, }, focusRingWidth: '2px', borderRadius: semanticRadii.l1, [tagsInputClearTriggerIconVars.width]: sizes._5, [tagsInputClearTriggerIconVars.height]: sizes._5, },})
const tagsInputClearTriggerIcon = stylex.create({ base: { width: tagsInputClearTriggerIconVars.width, height: tagsInputClearTriggerIconVars.height, },})
export type TagsInputSize = keyof typeof tagsInputRootSizes
export type TagsInputVariant = keyof typeof tagsInputControlVariants
const tagsInputRecipe = defineSlotRecipe({ slots: { root: { styles: tagsInputRoot, sizes: tagsInputRootSizes }, label: { styles: tagsInputLabel }, control: { styles: tagsInputControl, variants: tagsInputControlVariants }, input: { styles: tagsInputInput }, itemText: { styles: tagsInputItemText }, itemInput: { styles: tagsInputItemInput }, itemPreview: { styles: tagsInputItemPreview, variants: tagsInputItemPreviewVariants }, itemDeleteTrigger: { styles: tagsInputItemDeleteTrigger }, clearTrigger: { styles: tagsInputClearTrigger }, }, defaultVariants: { size: "md", variant: "outline" },});
const chrome = stylex.create({ btnReset: { appearance: "none", backgroundColor: "transparent", borderWidth: 0, borderStyle: "none", padding: 0, margin: 0, fontFamily: "inherit", fontSize: "inherit", color: "inherit", cursor: "pointer", textAlign: "inherit", },});
function CloseIcon(props: React.ComponentProps<"svg">) { return ( <svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true" {...props}> <path d="m4 4 8 8m0-8-8 8" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" /> </svg> );}
const { withProvider, withContext } = createSlotRecipeContext(tagsInputRecipe);
const StyledItemDeleteTrigger = withContext( ArkTagsInput.ItemDeleteTrigger, (v) => [chrome.btnReset, ...resolveSlotStyles(tagsInputRecipe.slots.itemDeleteTrigger, v)], "TagsInputItemDeleteTrigger",);
function ItemDeleteTrigger( props: React.ComponentProps<typeof ArkTagsInput.ItemDeleteTrigger> & StyleOverrideProps,) { const { children, ...rest } = props; return ( <StyledItemDeleteTrigger {...rest}> {children ?? <CloseIcon {...stylex.props(tagsInputItemDeleteTriggerIcon.base)} />} </StyledItemDeleteTrigger> );}ItemDeleteTrigger.displayName = "TagsInputItemDeleteTrigger";
const StyledClearTrigger = withContext( ArkTagsInput.ClearTrigger, (v) => [chrome.btnReset, ...resolveSlotStyles(tagsInputRecipe.slots.clearTrigger, v)], "TagsInputClearTrigger",);
function ClearTrigger( props: React.ComponentProps<typeof ArkTagsInput.ClearTrigger> & StyleOverrideProps,) { const { children, ...rest } = props; return ( <StyledClearTrigger {...rest}> {children ?? <CloseIcon {...stylex.props(tagsInputClearTriggerIcon.base)} />} </StyledClearTrigger> );}ClearTrigger.displayName = "TagsInputClearTrigger";
/** * TagsInput — Ark UI's tags-input machine parts styled with the StyleX slot * recipe. `<TagsInput.Root variant size>` resolves the recipe once; every * part inherits it through context. ItemDeleteTrigger and ClearTrigger ship * a default close icon. */export const TagsInput = { Root: withProvider(ArkTagsInput.Root, "root", "TagsInputRoot"), Label: withContext(ArkTagsInput.Label, "label", "TagsInputLabel"), Control: withContext(ArkTagsInput.Control, "control", "TagsInputControl"), Input: withContext(ArkTagsInput.Input, "input", "TagsInputInput"), Item: ArkTagsInput.Item, ItemPreview: withContext(ArkTagsInput.ItemPreview, "itemPreview", "TagsInputItemPreview"), ItemText: withContext(ArkTagsInput.ItemText, "itemText", "TagsInputItemText"), ItemInput: withContext(ArkTagsInput.ItemInput, "itemInput", "TagsInputItemInput"), ItemDeleteTrigger, ClearTrigger, HiddenInput: ArkTagsInput.HiddenInput, Context: ArkTagsInput.Context,};