Inputs
Colored Categories
Tags with coloured categories: a flat row of fifteen identical chips does not read, while colour groups faster than text.
- tags
- categories
- color
- form
Preview
Use it with AI
- 1. Copy the link.
- 2. Write to your agent in your own words and drop the link into the sentence.
- 3. The agent opens the link and installs the component from the registry.
put this in the header: https://vibeui.ru/c/tags-006?lang=en
Категория задаётся до ввода — цвет метки берётся из её названия
Show the full instructions
What the link says, spelled out. Use it when your agent cannot open links — paste this text instead.
# Install and place "tags-006" (Colored Categories) from VibeUI
## 1. Install first — do not skip, do not recreate
Run this exact command before writing any code:
npx shadcn@latest add https://vibeui.ru/r/tags-006.json
Registry item: https://vibeui.ru/r/tags-006.json
Installs to: components/vibeui/tags-006.tsx (the exact path follows this project's components.json aliases).
npm dependencies: none.
Registry dependencies: none.
Install it from the registry. Do not recreate it from the description,
do not substitute a similar component from another library, and do not
rewrite it to match the project's existing style.
## 2. What it is
Tags with coloured categories: a flat row of fifteen identical chips does not read, while colour groups faster than text.
Tags with categories where the chip colour is derived from the category name and the category is chosen before typing. Zero dependencies, one file.
## 3. How to use it
import { Tags006 } from "@/components/vibeui/tags-006"
<Tags006 label="Task labels" groups={["team", "urgency"]} defaultValue={[{ text: "design", group: "team" }]} />
Read the installed file for the full prop list.
## 4. Where to place it
This is a small inline component. Put it exactly where the user asked,
inside the existing markup. Do not create a new page, section or
wrapper for it. If a similar control already sits in that spot,
replace it instead of adding a second one.
Placement: ___
(The user fills this line in. If it is still blank, ask where to put it
instead of guessing.)
## 5. Keep exactly as installed
- the local --vibeui-tags-006-* palette — do not swap it for your theme tokens
- its own light surface: the field gets shown over any background
- deriving the hue from the name: a colour list would have to be maintained and kept in sync with the categories
- building all three colours from one hue in oklch: contrast then holds at any hue
- the dot next to the text: background colour must not be the only carrier of meaning
- the category inside the remove button's aria-label: colour is unavailable to a screen reader
- picking the category before typing: assigning it afterwards adds an extra step
## 6. You may change
- label — the field caption
- groups — the category list
- defaultValue — the starting tags together with their categories
- the lightness and chroma constants of the chip colours
- the accent through the accent prop
## 7. Rules
- The hash gives 12 hues: the thirteenth category repeats a colour, which is a deliberate trade-off.
- Renaming a category changes its colour — store the category as an id for stability.
- Colour does not replace the caption: the category has to be available as text.
- Do not lift the CSS variables into globals.css: the component has to stay a single file.
## 8. Verify
- it renders with no console errors;
- it looks like the preview on the VibeUI page you copied this from;
- if it does not, you changed something listed in section 5 — put it back.For developers
npx shadcn@latest add https://vibeui.ru/r/tags-006.jsonhttps://vibeui.ru/r/tags-006.jsonКомпонент самодостаточен: один файл, ноль зависимостей, палитра в локальных переменных --vibeui-tags-006-*. Клиентский: список тегов с категориями, черновик и выбранная категория в состоянии. Оттенок не выбирается вручную, а считается из названия категории хеш-функцией FNV и подставляется в --vibeui-tags-006-hue, поэтому новая категория сразу получает стабильный цвет и палитру не нужно вести отдельным списком. Все три цвета чипа — фон, текст и точка — строятся из одного оттенка в oklch, поэтому контраст не ломается на любом hue.
Component source
The same file your agent installs. Here in case you would rather copy it by hand.
"use client"
import { useId, useState } from "react"
import type {
ComponentPropsWithoutRef,
CSSProperties,
KeyboardEvent,
} from "react"
export type Tags006Props = Omit<
ComponentPropsWithoutRef<"div">,
"children" | "defaultValue"
> & {
label?: string
groups?: string[]
defaultValue?: { text: string; group: string }[]
accent?: string
}
// Идея компонента: у тега есть категория, и она видна цветом. Плоский список
// из пятнадцати одинаковых чипов не читается — глазу не за что зацепиться,
// пока он не прочитает каждый. Цвет группируется быстрее текста, поэтому
// «команда» и «срочность» различимы до чтения. Оттенок не выбирается вручную,
// а считается из названия категории хеш-функцией: новая категория сразу
// получает свой стабильный цвет, и палитру не нужно вести отдельным списком.
const STYLES = `
:where([data-vibeui-block="tags-006"]){
--vibeui-tags-006-surface:oklch(1 0 0);
--vibeui-tags-006-field:oklch(1 0 0);
--vibeui-tags-006-shell:oklch(0.9 0.006 265);
--vibeui-tags-006-fg:oklch(0.23 0.014 265);
--vibeui-tags-006-muted:oklch(0.55 0.014 265);
--vibeui-tags-006-border:oklch(0.88 0.008 265);
--vibeui-tags-006-accent:oklch(0.5 0.15 265);
--vibeui-tags-006-font:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
--vibeui-tags-006-hue:265;
}
/* Своя светлая подложка: поле показывают поверх любого фона. */
[data-vibeui-block="tags-006"]{
display:flex;flex-direction:column;gap:0.5rem;
width:100%;max-width:23rem;box-sizing:border-box;padding:0.875rem;
background:var(--vibeui-tags-006-surface);
border:1px solid var(--vibeui-tags-006-shell);border-radius:0.875rem;
font-family:var(--vibeui-tags-006-font);color:var(--vibeui-tags-006-fg);
}
[data-vibeui-block="tags-006"] label{font-size:0.8125rem;font-weight:650}
[data-vibeui-block="tags-006"] [data-part="field"]{
display:flex;flex-wrap:wrap;align-items:center;gap:0.375rem;
min-height:3rem;padding:0.375rem 0.5rem;box-sizing:border-box;
background:var(--vibeui-tags-006-field);
border:1px solid var(--vibeui-tags-006-border);border-radius:0.625rem;
}
[data-vibeui-block="tags-006"] [data-part="field"]:focus-within{
border-color:var(--vibeui-tags-006-accent);
box-shadow:0 0 0 2px oklch(0.5 0.15 265 / 18%);
}
/* Цвет считается из названия категории: палитру не надо вести отдельно. */
[data-vibeui-block="tags-006"] [data-part="chip"]{
display:inline-flex;align-items:center;gap:0.375rem;
height:1.75rem;padding:0 0.25rem 0 0.5rem;border-radius:0.4375rem;
background:oklch(0.94 0.05 var(--vibeui-tags-006-hue));
color:oklch(0.38 0.12 var(--vibeui-tags-006-hue));
font-size:0.8125rem;font-weight:650;
}
[data-vibeui-block="tags-006"] [data-part="dot"]{
width:0.4375rem;height:0.4375rem;border-radius:9999px;flex:none;
background:oklch(0.6 0.16 var(--vibeui-tags-006-hue));
}
[data-vibeui-block="tags-006"] [data-part="chip"] button{
appearance:none;border:0;background:none;cursor:pointer;
width:1.125rem;height:1.125rem;border-radius:0.3125rem;
color:inherit;font:inherit;font-size:0.875rem;line-height:1;opacity:.7;
}
[data-vibeui-block="tags-006"] [data-part="chip"] button:hover{opacity:1}
[data-vibeui-block="tags-006"] [data-part="chip"] button:focus-visible{outline:2px solid currentColor;outline-offset:1px}
[data-vibeui-block="tags-006"] [data-part="entry"]{display:flex;gap:0.375rem;align-items:stretch}
[data-vibeui-block="tags-006"] select{
flex:none;appearance:none;cursor:pointer;
padding:0 0.5rem;box-sizing:border-box;height:2.25rem;
border:1px solid var(--vibeui-tags-006-border);border-radius:0.5rem;
background:oklch(0.96 0.03 var(--vibeui-tags-006-hue));
color:oklch(0.38 0.12 var(--vibeui-tags-006-hue));
font:inherit;font-size:0.75rem;font-weight:650;
}
[data-vibeui-block="tags-006"] select:focus-visible{outline:2px solid var(--vibeui-tags-006-accent);outline-offset:1px}
[data-vibeui-block="tags-006"] input{
flex:1 1 auto;min-width:0;box-sizing:border-box;height:2.25rem;padding:0 0.625rem;
border:1px solid var(--vibeui-tags-006-border);border-radius:0.5rem;
background:var(--vibeui-tags-006-field);color:inherit;
font:inherit;font-size:0.875rem;
}
[data-vibeui-block="tags-006"] input:focus-visible{outline:2px solid var(--vibeui-tags-006-accent);outline-offset:1px;border-color:var(--vibeui-tags-006-accent)}
[data-vibeui-block="tags-006"] [data-part="hint"]{margin:0;font-size:0.75rem;color:var(--vibeui-tags-006-muted)}
@media (prefers-reduced-motion:reduce){[data-vibeui-block="tags-006"] *{animation:none!important;transition:none!important}}
`
const DEFAULT_GROUPS = ["команда", "срочность", "продукт"]
const DEFAULT_TAGS = [
{ text: "дизайн", group: "команда" },
{ text: "горит", group: "срочность" },
{ text: "каталог", group: "продукт" },
]
// Оттенок из названия: одна и та же категория всегда получает один цвет.
function hue(name: string) {
let hash = 2166136261
for (const symbol of name) {
hash ^= symbol.codePointAt(0) ?? 0
hash = Math.imul(hash, 16777619)
}
return ((hash >>> 0) % 12) * 30
}
/**
* Теги с цветными категориями: оттенок считается из названия категории.
* Один файл, ноль зависимостей, собственная палитра.
*/
export function Tags006({
label = "Метки задачи",
groups = DEFAULT_GROUPS,
defaultValue = DEFAULT_TAGS,
accent,
className,
style,
...props
}: Tags006Props) {
const id = useId()
const [tags, setTags] = useState(defaultValue)
const [draft, setDraft] = useState("")
const [group, setGroup] = useState(groups[0])
const add = () => {
const text = draft.trim()
if (!text || tags.some((tag) => tag.text === text)) return setDraft("")
setTags([...tags, { text, group }])
setDraft("")
}
const onKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
if (event.key !== "Enter") return
event.preventDefault()
add()
}
const palette = {
...(accent ? { "--vibeui-tags-006-accent": accent } : null),
...style,
} as CSSProperties
return (
<>
<style href="vibeui-tags-006" precedence="medium">
{STYLES}
</style>
<div
{...props}
data-vibeui-block="tags-006"
className={className}
style={palette}
>
<label htmlFor={id}>{label}</label>
<div data-part="field">
{tags.map((tag) => (
<span
key={tag.text}
data-part="chip"
style={
{ "--vibeui-tags-006-hue": hue(tag.group) } as CSSProperties
}
>
<span data-part="dot" aria-hidden="true" />
{tag.text}
<button
type="button"
aria-label={`Убрать ${tag.text} из категории ${tag.group}`}
onClick={() =>
setTags(tags.filter((item) => item.text !== tag.text))
}
>
×
</button>
</span>
))}
</div>
<div
data-part="entry"
style={{ "--vibeui-tags-006-hue": hue(group) } as CSSProperties}
>
<select
value={group}
aria-label="Категория тега"
onChange={(event) => setGroup(event.target.value)}
>
{groups.map((item) => (
<option key={item} value={item}>
{item}
</option>
))}
</select>
<input
id={id}
type="text"
value={draft}
placeholder="Новая метка…"
aria-describedby={`${id}-hint`}
onChange={(event) => setDraft(event.target.value)}
onKeyDown={onKeyDown}
/>
</div>
<p id={`${id}-hint`} data-part="hint">
Категория задаётся до ввода — цвет метки берётся из её названия
</p>
</div>
</>
)
}