@xsolla/xui-tag
v0.209.1
Published
A cross-platform React tag component for displaying labels, categories, and removable chips. Supports multiple tones, solid/outlined types, and optional left/right icons. <!-- BEGIN:xui-mcp-instructions:tag --> A compact label chip used to categorise, cla
Readme
Tag
A cross-platform React tag component for displaying labels, categories, and removable chips. Supports multiple tones, solid/outlined types, and optional left/right icons.
A compact label chip used to categorise, classify, or annotate content. Renders as a pill-shaped container with a text label and optional leading and trailing icons. Supports two visual types (solid fill and outlined), seven semantic tones, and five sizes. Used in lists, cards, table cells, filters, and any context where a short categorical attribute needs to be visually marked.
When to use
- To display a category, status, or attribute as a persistent visual marker — e.g. "New", "Beta", "Sale", "Featured"
- To represent a selected filter or active facet in a filter bar
- To show a label on a card, list item, or table row when a colour-coded semantic signal is needed
- In multi-tag groups where several attributes must be shown simultaneously on one item
When not to use
- For interactive toggleable filters — use ToggleButtonGroup or a checkable chip pattern
- For operational statuses with complex state logic — use StatusDropdown or Badge
- For dismissible user-entered tags — add a remove (✕) button in the Icon right slot
- When the label is purely decorative with no semantic meaning — use a styled text span instead
Content guidelines
- Label text — keep tag labels short: 1–3 words maximum. Use title case for category labels ("New", "On Sale", "Beta") and sentence case for descriptive labels ("Out of stock").
- Avoid redundancy — if an icon already communicates the meaning (e.g. a checkmark for verified), the label "Verified" may be redundant in very compact contexts. In most cases, keep both for clarity.
- Consistent vocabulary — use the same label text for the same semantic state across the entire product. Do not use "Beta" in one place and "Preview" in another for the same concept.
- No punctuation — do not end tag labels with periods, commas, or colons.
Behaviour guidelines
- Read-only vs interactive — by default, Tag is a display component with no built-in interactive states. If a tag must be dismissible, add a trailing remove icon (Icon right=true with an ✕ icon) and handle the click at the product level. If a tag must be selectable/toggleable, consider ToggleButtonGroup instead.
- Removable tags — when using Icon right as a remove button, the click target for removal should be the icon alone, not the entire tag. Give the remove icon an aria-label="Remove [tag label]" and handle the action separately from any click on the tag's label area.
- Tag groups — when multiple tags appear together (e.g. on a product card), render them in a wrapping flex row with consistent gap. Define a maximum number of visible tags and show a "+N more" indicator when exceeded.
- Overflow — tag labels must not wrap to multiple lines. If the label is too long to fit on one line within the chosen size, truncate with ellipsis and show the full text in a tooltip.
- Colour consistency — use the same Tone for the same semantic category across all surfaces in the product. Establish a tone-to-category mapping in the product's design token system and apply it uniformly.
- Type selection — use Type=Solid as the default. Switch to Type=Outlined when the tag is placed on a coloured surface (e.g. inside a coloured card or over a photo) where a solid fill would be visually too heavy.
Accessibility
- If the tag is a purely decorative label with no actionable meaning, it can have role="presentation" and aria-hidden="true" when the surrounding context already communicates its meaning.
- If the tag communicates meaningful state information (e.g. "New", "Sale", "Out of stock"), it must be readable by screen readers. Ensure it is not hidden from the accessibility tree.
- Do not rely on Tone (colour) alone to communicate meaning — the label text or icon must also be present (WCAG 1.4.1 Use of Colour). For icon-only tags, provide aria-label on the element.
- Removable tags: the remove button (Icon right) must have aria-label="Remove [tag name]" so screen readers can announce which tag is being removed. The button must be focusable and respond to Enter / Space.
- When tags are inside a list, wrap them in a with elements and an aria-label on the list describing the group — e.g. aria-label="Product tags".
- Ensure sufficient colour contrast between the tag label text and the tag background in both Solid and Outlined variants, for all tones, meeting WCAG AA (4.5:1 for small text at XS/S sizes).
Installation
npm install @xsolla/xui-tagDemo
Basic Tag
import * as React from "react";
import { Tag } from "@xsolla/xui-tag";
export default function BasicTag() {
return (
<div style={{ display: "flex", gap: 8 }}>
<Tag>Default</Tag>
<Tag tone="brand">Brand</Tag>
<Tag tone="success">Success</Tag>
</div>
);
}Tag Tones
import * as React from "react";
import { Tag } from "@xsolla/xui-tag";
export default function TagTones() {
return (
<div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
<Tag tone="primary">Primary</Tag>
<Tag tone="brand">Brand</Tag>
<Tag tone="brandExtra">Brand Extra</Tag>
<Tag tone="success">Success</Tag>
<Tag tone="warning">Warning</Tag>
<Tag tone="alert">Alert</Tag>
<Tag tone="neutral">Neutral</Tag>
</div>
);
}Tag Sizes
import * as React from "react";
import { Tag } from "@xsolla/xui-tag";
export default function TagSizes() {
return (
<div style={{ display: "flex", gap: 8, alignItems: "center" }}>
<Tag size="xs">Extra Small</Tag>
<Tag size="sm">Small</Tag>
<Tag size="md">Medium</Tag>
<Tag size="lg">Large</Tag>
<Tag size="xl">Extra Large</Tag>
</div>
);
}Solid vs Outlined
import * as React from "react";
import { Tag } from "@xsolla/xui-tag";
export default function TagTypes() {
return (
<div style={{ display: "flex", gap: 8 }}>
<Tag tone="brand" type="solid">
Solid
</Tag>
<Tag tone="brand" type="outlined">
Outlined
</Tag>
</div>
);
}Tag with Icons
import * as React from "react";
import { Tag } from "@xsolla/xui-tag";
import { Check } from "@xsolla/xui-icons";
import { Star, Clock } from "@xsolla/xui-icons-base";
export default function TagWithIcon() {
return (
<div style={{ display: "flex", gap: 8 }}>
<Tag iconLeft={<Star size={12} />} tone="warning">
Featured
</Tag>
<Tag iconLeft={<Check size={12} />} tone="success">
Verified
</Tag>
<Tag
iconLeft={<Clock size={12} />}
iconRight={<Star size={12} />}
tone="neutral"
>
Pending
</Tag>
</div>
);
}Icon-Only Tag
import * as React from "react";
import { Tag } from "@xsolla/xui-tag";
import { Check } from "@xsolla/xui-icons";
export default function IconOnlyTag() {
return <Tag iconLeft={<Check size={12} />} tone="success" />;
}Removable Tag
import * as React from "react";
import { Tag } from "@xsolla/xui-tag";
export default function RemovableTag() {
const [tags, setTags] = React.useState([
"React",
"TypeScript",
"Node.js",
"GraphQL",
]);
const removeTag = (tagToRemove: string) => {
setTags(tags.filter((tag) => tag !== tagToRemove));
};
return (
<div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
{tags.map((tag) => (
<Tag key={tag} tone="brand" onRemove={() => removeTag(tag)}>
{tag}
</Tag>
))}
</div>
);
}Anatomy
Import the component and use it directly:
import { Tag } from "@xsolla/xui-tag";
<Tag
size="md" // Size variant
tone="brand" // Color tone
type="solid" // Solid or outlined
iconLeft={<Icon />} // Optional leading icon
iconRight={<Icon />} // Optional trailing icon
onRemove={handleRemove} // Makes tag removable (renders X icon)
>
Tag Label
</Tag>;Examples
Category Tags
import * as React from "react";
import { Tag } from "@xsolla/xui-tag";
export default function CategoryTags() {
const categories = [
{ name: "Technology", tone: "brand" as const },
{ name: "Design", tone: "brandExtra" as const },
{ name: "Marketing", tone: "success" as const },
{ name: "Sales", tone: "warning" as const },
];
return (
<div style={{ display: "flex", gap: 8 }}>
{categories.map((cat) => (
<Tag key={cat.name} tone={cat.tone} size="sm">
{cat.name}
</Tag>
))}
</div>
);
}Status Tags
import * as React from "react";
import { Tag } from "@xsolla/xui-tag";
import { CheckCircle, Clock, XCircle } from "@xsolla/xui-icons-base";
export default function StatusTags() {
return (
<div style={{ display: "flex", gap: 8 }}>
<Tag iconLeft={<CheckCircle size={12} />} tone="success">
Completed
</Tag>
<Tag iconLeft={<Clock size={12} />} tone="warning">
In Progress
</Tag>
<Tag iconLeft={<XCircle size={12} />} tone="alert">
Failed
</Tag>
</div>
);
}Tag Input
import * as React from "react";
import { Tag } from "@xsolla/xui-tag";
import { Input } from "@xsolla/xui-input";
export default function TagInput() {
const [tags, setTags] = React.useState(["react", "typescript"]);
const [inputValue, setInputValue] = React.useState("");
const addTag = () => {
if (inputValue.trim() && !tags.includes(inputValue.trim())) {
setTags([...tags, inputValue.trim()]);
setInputValue("");
}
};
return (
<div>
<div
style={{ display: "flex", gap: 8, flexWrap: "wrap", marginBottom: 8 }}
>
{tags.map((tag) => (
<Tag
key={tag}
tone="primary"
onRemove={() => setTags(tags.filter((t) => t !== tag))}
>
{tag}
</Tag>
))}
</div>
<Input
value={inputValue}
onChangeText={setInputValue}
placeholder="Add a tag..."
onKeyDown={(e) => e.key === "Enter" && addTag()}
/>
</div>
);
}API Reference
Tag
A tag/chip component.
Tag Props:
| Prop | Type | Default | Description |
| :-------- | :--------------------------------------------------------------------------------------- | :---------- | :------------------------------------------------------------------------------------------------------------ |
| testID | string | — | Test ID for testing frameworks. On web this renders as data-testid; on React Native it renders as testID. |
| children | ReactNode | - | Tag content. Optional for icon-only tags. |
| size | "xl" \| "lg" \| "md" \| "sm" \| "xs" | "md" | Size of the tag. |
| tone | "primary" \| "brand" \| "brandExtra" \| "success" \| "warning" \| "alert" \| "neutral" | "primary" | Color tone. |
| type | "solid" \| "outlined" | "solid" | Visual type. Solid fills background; outlined uses a border with lighter background. |
| iconLeft | ReactNode | - | Leading icon. |
| iconRight | ReactNode | - | Trailing icon. |
| onRemove | () => void | - | Callback for remove button. Renders an X icon in the trailing position. |
Solid Tone Color Mapping:
| Tone | Background | Text | | :--------- | :----------------- | :-------------- | | primary | Background primary | Content primary | | brand | Brand primary | On brand | | brandExtra | BrandExtra primary | On brandExtra | | success | Success primary | On success | | warning | Warning primary | On warning | | alert | Alert primary | On alert | | neutral | Neutral primary | On neutral |
Outlined Tone Color Mapping:
| Tone | Background | Border | Text | | :--------- | :------------------- | :---------------- | :------------------------- | | primary | Background primary | Border secondary | Content primary | | brand | Brand secondary | Border brand | Content brand primary | | brandExtra | BrandExtra secondary | Border brandExtra | Content brandExtra primary | | success | Success secondary | Border success | Content success primary | | warning | Warning secondary | Border warning | Content warning primary | | alert | Alert secondary | Border alert | Content alert primary | | neutral | Neutral secondary | Border neutral | Content neutral primary |
Accessibility
- Uses semantic elements for proper structure
- Remove button includes accessible label
- Keyboard accessible remove action
- Focus indicator on interactive elements
