All components
Tag
Compact labels for filtering and metadata with subtle, solid, outline, and surface variants.
StyleX
12345678910111213141516import { Tag } from "#components/ui/ark-ui/tag.tsx";
export function Example() { return ( // variant/size resolve once on Root; every part inherits them via context <Tag.Root variant="subtle" size="md"> <Tag.Label>StyleX</Tag.Label> <Tag.CloseTrigger onClick={remove} aria-label="Remove tag"> <CloseIcon /> </Tag.CloseTrigger> </Tag.Root> );}
// slots for adornments: <Tag.StartElement> / <Tag.EndElement> size icons// and avatars to the tag's scaleVariants
StyleXsubtle
StyleXsolid
StyleXoutline
StyleXsurface
Sizes
StyleXsm
StyleXmd
StyleXlg
StyleXxl
Install
1npx stylexblocks add tagSource
components/ui/ark-ui/tag.tsx
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261import * as stylex from "@stylexjs/stylex";import { createSlotRecipeContext } from "#lib/ark-ui/create-slot-recipe-context.tsx";import { defineSlotRecipe } from "#lib/recipe-props.ts";import { tagStartElementIconVars, tagEndElementIconVars } from '@stylexblocks/ui/slot-recipes/tag.stylex.ts'
import { colorPalette } from '@stylexblocks/ui/color-palette.stylex.ts'import { cursor } from '@stylexblocks/ui/tokens/cursor.stylex.ts'import { fontSizes } from '@stylexblocks/ui/tokens/font-sizes.stylex.ts'import { lineHeights } from '@stylexblocks/ui/tokens/line-heights.stylex.ts'import { radii } from '@stylexblocks/ui/tokens/radii.stylex.ts'import { sizes } from '@stylexblocks/ui/tokens/sizes.stylex.ts'import { spacing } from '@stylexblocks/ui/tokens/spacing.stylex.ts'
const tagRoot = stylex.create({ base: { display: 'inline-flex', alignItems: 'center', verticalAlign: 'top', maxWidth: '100%', userSelect: 'none', borderRadius: radii.full, outlineWidth: { default: null, ':focus-visible': '1px', }, outlineOffset: { default: null, ':focus-visible': '2px', }, outlineStyle: { default: null, ':focus-visible': 'solid', }, outlineColor: { default: null, ':focus-visible': colorPalette.focusRing, }, },})
const tagRootSizes = stylex.create({ sm: { paddingInline: spacing._1_5, minHeight: sizes._5, gap: spacing._1, '--tag-avatar-size': spacing._3_5, '--tag-status-size': spacing._2, '--tag-element-size': spacing._3_5, '--tag-element-offset': spacing._0_5, }, md: { paddingInline: spacing._2, minHeight: sizes._6, gap: spacing._1, '--tag-avatar-size': spacing._4, '--tag-status-size': spacing._2, '--tag-element-size': spacing._4, '--tag-element-offset': spacing._1, }, lg: { paddingInline: spacing._2_5, minHeight: sizes._7, gap: spacing._1, '--tag-avatar-size': spacing._5, '--tag-status-size': spacing._2, '--tag-element-size': spacing._5, '--tag-element-offset': spacing._1_5, }, xl: { paddingInline: spacing._3, minHeight: sizes._8, gap: spacing._1_5, '--tag-avatar-size': spacing._6, '--tag-status-size': spacing._2, '--tag-element-size': spacing._6, '--tag-element-offset': spacing._2, },})
const tagRootVariants = stylex.create({ subtle: { backgroundColor: colorPalette.subtle, color: colorPalette.fg, }, solid: { backgroundColor: colorPalette.solid, color: colorPalette.contrast, }, outline: { color: colorPalette.fg, boxShadow: 'inset 0 0 0px 1px var(--shadow-color)', '--shadow-color': colorPalette.border, }, surface: { backgroundColor: colorPalette.muted, color: colorPalette.fg, boxShadow: 'inset 0 0 0px 1px var(--shadow-color)', '--shadow-color': colorPalette.border, },})
const tagLabel = stylex.create({ base: { lineClamp: 1, },})
const tagLabelSizes = stylex.create({ sm: { fontSize: fontSizes.xs, lineHeight: lineHeights.xs, }, md: { fontSize: fontSizes.xs, lineHeight: lineHeights.xs, }, lg: { fontSize: fontSizes.sm, lineHeight: lineHeights.sm, }, xl: { fontSize: fontSizes.md, lineHeight: lineHeights.md, },})
const tagCloseTrigger = stylex.create({ base: { cursor: cursor.button, position: 'relative', display: 'flex', alignItems: 'center', justifyContent: 'center', outline: 'none', borderRadius: radii.full, color: 'currentColor', opacity: { default: 0.8, ':hover': 1, }, padding: '1px', 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', backgroundColor: { default: null, ':hover': colorPalette.subtle, }, '::after': { content: '""', position: 'absolute', width: '24px', height: '24px', borderRadius: radii.full, }, },})
const tagCloseTriggerSizes = stylex.create({ lg: { padding: '2px', }, xl: { padding: '3px', },})
const tagStartElement = stylex.create({ base: { display: 'inline-flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0, width: 'var(--tag-element-size)', height: 'var(--tag-element-size)', [tagStartElementIconVars.width]: '80% !important', [tagStartElementIconVars.height]: '80% !important', },})
const tagStartElementIcon = stylex.create({ base: { width: tagStartElementIconVars.width, height: tagStartElementIconVars.height, },})
const tagEndElement = stylex.create({ base: { flexShrink: 0, width: 'var(--tag-element-size)', height: 'var(--tag-element-size)', [tagEndElementIconVars.width]: '100%', [tagEndElementIconVars.height]: '100%', },})
const tagEndElementIcon = stylex.create({ base: { width: tagEndElementIconVars.width, height: tagEndElementIconVars.height, },})
export type TagSize = keyof typeof tagRootSizes
export type TagVariant = keyof typeof tagRootVariants
// defineSlotRecipe validates the shape at compile time: default values must// exist in the axis maps, and slot keys must be `styles` or a declared axisconst tagSlotRecipe = defineSlotRecipe({ slots: { root: { styles: tagRoot, sizes: tagRootSizes, variants: tagRootVariants }, label: { styles: tagLabel, sizes: tagLabelSizes }, startElement: { styles: tagStartElement }, endElement: { styles: tagEndElement }, closeTrigger: { styles: tagCloseTrigger }, }, defaultVariants: { size: "md", variant: "surface" },});
/** * Tag — recipe and component colocated in one file. `<Tag.Root variant * size>` resolves the recipe once and every part inherits it through * context; each part composes via `asChild`. */const { withProvider, withContext } = createSlotRecipeContext(tagSlotRecipe);
export const Tag = { // slot names resolve mechanically from the recipe maps above Root: withProvider("span", "root", "TagRoot"), Label: withContext("span", "label", "TagLabel"), StartElement: withContext("span", "startElement", "TagStartElement"), EndElement: withContext("span", "endElement", "TagEndElement"), // explicit resolver: the close trigger's size map is sparse (lg/xl only) — // TypeScript errors here the moment a styled size leaves the map above CloseTrigger: withContext( "button", (v) => [ tagCloseTrigger.base, (v.size === "lg" || v.size === "xl") && tagCloseTriggerSizes[v.size], ], "TagCloseTrigger", ),};