Tag list
Wrapping tags that collapse into a +N count past a limit.
npx shadcn@latest add https://ui.goodtabs.co/r/tag-list.json
With the @goodtabs namespace in components.json: npx shadcn@latest add @goodtabs/tag-list
- react
- accessibility
- and 4 more: motion, tailwind, forms, charts
Two tags shown, the rest counted in a mono chip.
Wrapping
- next.js
- react
- typescript
- tailwind
- shadcn
- motion
- vercel
- postgres
- zod
No limit in play, so tags flow onto new lines.
In a list
Command palette
- cmdk
- search
- keyboard
- and 1 more: overlay
Date field
- forms
- calendar
Stat card
- dashboard
- metric
- kpi
- and 2 more: card, trend
One row per item, each with its own tags.
Usage
import { TagList } from "@/components/ui/tag-list"
<TagList tags={["react", "motion", "forms"]} max={2} />Props
| Prop | Type | Default | Description |
|---|---|---|---|
| tags | string[] | The tags, in display order. | |
| max | number | 4 | Tags shown before the rest collapse into +N. |
| label | string | "Tags" | Accessible name for the list. |
Source
tag-list/tag-list.tsx
import * as React from "react";
import { cn } from "@/lib/utils";
type TagListProps = Omit<React.ComponentProps<"ul">, "children"> & {
/** The tags, in the order they should show. */
tags: string[];
/** How many tags to show before the rest collapse into a count. */
max?: number;
/** Accessible name for the list. */
label?: string;
};
/**
* Tags that wrap onto as many lines as they need. Past `max`, the rest collapse
* into a "+N" chip whose title lists them, and the full list stays in the markup
* for screen readers.
*/
export function TagList({ tags, max = 4, label = "Tags", className, ...props }: TagListProps) {
const shown = tags.slice(0, max);
const hidden = tags.slice(max);
const chip = "inline-flex h-6 items-center rounded-full border border-border px-2.5 text-xs whitespace-nowrap";
return (
<ul aria-label={label} className={cn("m-0 flex list-none flex-wrap gap-1.5 p-0", className)} {...props}>
{shown.map((tag, i) => (
<li key={i} className={cn(chip, "bg-muted text-foreground")}>
{tag}
</li>
))}
{hidden.length > 0 && (
<li title={hidden.join(", ")} className={cn(chip, "font-mono text-muted-foreground tabular-nums")}>
<span aria-hidden="true">+{hidden.length}</span>
<span className="sr-only">{`and ${hidden.length} more: ${hidden.join(", ")}`}</span>
</li>
)}
</ul>
);
}
For AI agents
Hand your agent this page, the markdown at /components/tag-list/prompt.md, or the Copy AI prompt button above.
When to use
- Cards and rows that carry a few labels under a title
- Any set of short, non-interactive keywords
- Places where a long tag list must not break the layout
When not to use
- Tags a person can add or remove: use tag-input
- Tags that filter a list: use filter-chip
Agent notes
- Tags are static text in list items, not buttons or links
- The +N chip carries a title and a screen reader sentence naming the hidden tags
- Wrapping is plain flex-wrap; set a width on the list or its parent
- Pass max larger than the count to show everything