Use it for
Use BbTag when users invent the values, such as recipients, skills, or issue labels.
Pass Through
Hover or tap a part to outline it. Toggles flip loading, errors and warnings when the component has them — only parts highlight.
Invite recipients with feedback
Seed a string[] model and show feedback for values rejected by the cap or duplicate check.
2 of 5 invited
<template>
<div class="flex max-w-md flex-col gap-2">
<BbTag
id="invite-recipients"
v-model="recipients"
autocomplete="email"
label="Invite people"
:max="5"
name="recipients"
placeholder="Add an email address"
prepend:icon="lucide:mail"
:warnings="warning"
@duplicate="onDuplicate"
@max="onMax"
@update:model-value="warning = []"
/>
<p class="text-sm opacity-70">{{ recipients.length }} of 5 invited</p>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { BbTag } from 'bitboss-ui';
import { users } from '~/demo-data';
const recipients = ref<string[]>([users[0]!.email, users[1]!.email]);
const warning = ref<string[]>([]);
// A refused entry is reported, not rendered. Showing it is our job — try
// re-adding an address that is already there, or adding a sixth one.
const onDuplicate = (value: string) => {
warning.value = [`${value} is already on the list.`];
};
const onMax = (value: string) => {
warning.value = [`Five invites is the cap, so ${value} was not added.`];
};
</script>
duplicate and max report the refused string but render no message. Put application wording in warnings or a toast.
Coming from v2
modelValue is now strictly string[]; object arrays no longer compile. The removed multiple prop was always true, so delete it.
Commit and normalize entries
divider chooses the commit key. Add comma only when commas cannot belong inside a valid token.
bug · needs-triage
<template>
<div class="flex max-w-md flex-col gap-2">
<!-- Either key commits the current text. A comma also splits pasted text. -->
<BbTag
id="divider-labels"
v-model="labels"
:divider="[',', 'Enter']"
hint="Separate labels with a comma, or press Enter."
label="Issue labels"
name="labels"
persistent-hint
placeholder="bug, regression, needs-triage"
/>
<p class="text-sm opacity-70">
{{ labels.join(' · ') || 'No labels yet' }}
</p>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { BbTag } from 'bitboss-ui';
const labels = ref<string[]>(['bug', 'needs-triage']);
</script>
Entries are trimmed. Duplicate matching is case-insensitive in v3; use case-sensitive only when case changes identity.
Edit tokens without a pointer
With an empty input, Backspace highlights then removes, arrows move between chips, Home/End jump, and Escape returns to typing.
4 skills listed
<template>
<div class="flex max-w-md flex-col gap-2">
<BbTag
id="keyboard-skills"
v-model="chosen"
hint="Empty field: Backspace highlights, Backspace again removes, arrows move, Escape returns to typing."
label="Skills"
name="skills"
persistent-hint
placeholder="Add a skill"
/>
<p class="text-sm opacity-70">
{{ chosen.length }} skill{{ chosen.length === 1 ? '' : 's' }} listed
</p>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { BbTag } from 'bitboss-ui';
import { skills } from '~/demo-data';
// Four chips, so the highlight has somewhere to walk.
const chosen = ref<string[]>(skills.slice(0, 4));
</script>
Keep instructions in a hint when the workflow depends on keyboard editing. The input retains DOM focus and announces the highlighted chip.
Validate and lock the field
required is satisfied by the first token. Compute larger minimums yourself and pass the message through errors.
<template>
<div class="flex max-w-md flex-col gap-4">
<!-- `required` stops applying the moment the array has one entry. -->
<BbTag
id="states-keywords"
v-model="keywords"
:errors="keywordErrors"
label="Search keywords"
name="keywords"
placeholder="Add a keyword"
required
/>
<!-- A spinner, not a lock: the field stays editable while this runs. -->
<BbTag
id="states-aliases"
v-model="aliases"
hint="Checking these against the registry…"
label="Domain aliases"
loading
name="aliases"
persistent-hint
/>
<!-- readonly, not disabled: chips stay visible, focusable and copyable. -->
<BbTag
id="states-inherited"
label="Inherited from the workspace"
:model-value="inherited"
name="inherited"
readonly
/>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
import { BbTag } from 'bitboss-ui';
import { clone, initialTags } from '~/demo-data';
const keywords = ref<string[]>([]);
const aliases = ref<string[]>(['vantera.io', 'vantera.dev']);
const inherited = clone(initialTags);
const keywordErrors = computed(() =>
keywords.value.length === 0 ? ['Add at least one keyword.'] : []
);
</script>
loading is only status and does not lock editing. Use readonly for a set people may copy, disabled to remove it from the tab order, and clearable only when deleting the whole array is expected.
comma is a compact display mode with no per-chip remove buttons. For read-only tokens outside a field, render BbBadge instead.