TagGroup
A focusable list of tags with support for keyboard navigation, selection, and removal.
Import
import { AvTagGroupImports } from '@avesra/angular';Usage
import { Component } from '@angular/core';
import { AvTagGroupImports } from '@avesra/angular';
@Component({
selector: 'app-tag-group-basic-demo',
imports: [AvTagGroupImports],
template: `<av-tag-group selection-mode="single" aria-label="Tags">
<div av-tag-group-list>
<div av-tag value="news">News</div>
<div av-tag value="travel">Travel</div>
<div av-tag value="gaming">Gaming</div>
<div av-tag value="shopping">Shopping</div>
</div>
</av-tag-group>`,
})
export class TagGroupBasicDemo {}Anatomy
Compose av-tag-group with av-tag-group-list and av-tag items. Use shared av-label / av-description / av-error-message for field chrome. Optional button[av-tag-remove-button] customizes the remove control when allows-removing is set.
<av-tag-group>
<label av-label></label>
<div av-tag-group-list>
<div av-tag value="news">
News
<button av-tag-remove-button type="button"></button>
</div>
</div>
<p av-description></p>
<p av-error-message></p>
</av-tag-group>Sizes
import { Component } from '@angular/core';
import {
AvLabelComponent,
AvTagGroupImports,
} from '@avesra/angular';
@Component({
selector: 'app-tag-group-sizes-demo',
imports: [
AvTagGroupImports,
AvLabelComponent,
],
template: `<div class="flex flex-col gap-6">
<av-tag-group selection-mode="single" size="sm">
<label av-label>Small</label>
<div av-tag-group-list>
<div av-tag value="news">News</div>
<div av-tag value="travel">Travel</div>
<div av-tag value="gaming">Gaming</div>
</div>
</av-tag-group>
<av-tag-group selection-mode="single" size="md">
<label av-label>Medium</label>
<div av-tag-group-list>
<div av-tag value="news">News</div>
<div av-tag value="travel">Travel</div>
<div av-tag value="gaming">Gaming</div>
</div>
</av-tag-group>
<av-tag-group selection-mode="single" size="lg">
<label av-label>Large</label>
<div av-tag-group-list>
<div av-tag value="news">News</div>
<div av-tag value="travel">Travel</div>
<div av-tag value="gaming">Gaming</div>
</div>
</av-tag-group>
</div>`,
})
export class TagGroupSizesDemo {}Variants
import { Component } from '@angular/core';
import {
AvLabelComponent,
AvTagGroupImports,
} from '@avesra/angular';
@Component({
selector: 'app-tag-group-variants-demo',
imports: [
AvTagGroupImports,
AvLabelComponent,
],
template: `<div class="flex flex-col gap-8">
<av-tag-group selection-mode="single" variant="default">
<label av-label>Default</label>
<div av-tag-group-list>
<div av-tag value="news">News</div>
<div av-tag value="travel">Travel</div>
<div av-tag value="gaming">Gaming</div>
</div>
</av-tag-group>
<av-tag-group selection-mode="single" variant="surface">
<label av-label>Surface</label>
<div av-tag-group-list>
<div av-tag value="news">News</div>
<div av-tag value="travel">Travel</div>
<div av-tag value="gaming">Gaming</div>
</div>
</av-tag-group>
</div>`,
})
export class TagGroupVariantsDemo {}Disabled
Some tags are disabled
Tags disabled via disabled-keys prop
import { Component } from '@angular/core';
import {
AvDescriptionComponent,
AvLabelComponent,
AvTagGroupImports,
} from '@avesra/angular';
@Component({
selector: 'app-tag-group-disabled-demo',
imports: [
AvTagGroupImports,
AvLabelComponent,
AvDescriptionComponent,
],
template: `
<div class="flex flex-col gap-4">
<av-tag-group selection-mode="single">
<label av-label>Disabled Tags</label>
<div av-tag-group-list>
<div av-tag value="news" disabled>News</div>
<div av-tag value="travel">Travel</div>
<div av-tag value="gaming" disabled>Gaming</div>
</div>
<p av-description>Some tags are disabled</p>
</av-tag-group>
<av-tag-group selection-mode="single" [disabled-keys]="['travel']">
<label av-label>Disabled Keys</label>
<div av-tag-group-list>
<div av-tag value="news">News</div>
<div av-tag value="travel">Travel</div>
<div av-tag value="gaming">Gaming</div>
</div>
<p av-description>Tags disabled via disabled-keys prop</p>
</av-tag-group>
</div>
`,
})
export class TagGroupDisabledDemo {}Selection Modes
Choose one category
Choose multiple categories
import { Component, signal } from '@angular/core';
import {
AvDescriptionComponent,
AvLabelComponent,
AvTagGroupImports,
} from '@avesra/angular';
@Component({
selector: 'app-tag-group-selection-demo',
imports: [
AvTagGroupImports,
AvLabelComponent,
AvDescriptionComponent,
],
template: `<div class="flex flex-col gap-8">
<av-tag-group
selection-mode="single"
[default-selected-keys]="['news']"
[(selectedKeys)]="singleSelected"
>
<label av-label>Single Selection</label>
<div av-tag-group-list>
<div av-tag value="news">News</div>
<div av-tag value="travel">Travel</div>
<div av-tag value="gaming">Gaming</div>
<div av-tag value="shopping">Shopping</div>
</div>
<p av-description>Choose one category</p>
</av-tag-group>
<av-tag-group
selection-mode="multiple"
[default-selected-keys]="['news', 'travel']"
[(selectedKeys)]="multipleSelected"
>
<label av-label>Multiple Selection</label>
<div av-tag-group-list>
<div av-tag value="news">News</div>
<div av-tag value="travel">Travel</div>
<div av-tag value="gaming">Gaming</div>
<div av-tag value="shopping">Shopping</div>
</div>
<p av-description>Choose multiple categories</p>
</av-tag-group>
</div>`,
})
export class TagGroupSelectionDemo {
readonly singleSelected = signal<string[]>(['news']);
readonly multipleSelected = signal<string[]>(['news', 'travel']);
}Controlled
Selected: news, travel
import { Component, signal } from '@angular/core';
import {
AvDescriptionComponent,
AvLabelComponent,
AvTagGroupImports,
} from '@avesra/angular';
@Component({
selector: 'app-tag-group-controlled-demo',
imports: [
AvTagGroupImports,
AvLabelComponent,
AvDescriptionComponent,
],
template: `
<av-tag-group
selection-mode="multiple"
[(selectedKeys)]="selected"
>
<label av-label>Categories (controlled)</label>
<div av-tag-group-list>
<div av-tag value="news">News</div>
<div av-tag value="travel">Travel</div>
<div av-tag value="gaming">Gaming</div>
<div av-tag value="shopping">Shopping</div>
</div>
<p av-description>
Selected: {{ selected().length > 0 ? selected().join(', ') : 'None' }}
</p>
</av-tag-group>
`,
})
export class TagGroupControlledDemo {
readonly selected = signal<string[]>(['news', 'travel']);
}With Error Message
Select at least one category
import { Component, computed, signal } from '@angular/core';
import {
AvDescriptionComponent,
AvErrorMessageComponent,
AvLabelComponent,
AvTagGroupImports,
} from '@avesra/angular';
@Component({
selector: 'app-tag-group-with-error-message-demo',
imports: [
AvTagGroupImports,
AvLabelComponent,
AvDescriptionComponent,
AvErrorMessageComponent,
],
template: `
<av-tag-group selection-mode="multiple" [(selectedKeys)]="selected">
<label av-label>Amenities</label>
<div av-tag-group-list>
<div av-tag value="laundry">Laundry</div>
<div av-tag value="fitness">Fitness center</div>
<div av-tag value="parking">Parking</div>
<div av-tag value="pool">Swimming pool</div>
<div av-tag value="breakfast">Breakfast</div>
</div>
<p av-description>
{{
isInvalid()
? 'Select at least one category'
: 'Selected: ' + selected().join(', ')
}}
</p>
@if (isInvalid()) {
<p av-error-message>Please select at least one category</p>
}
</av-tag-group>
`,
})
export class TagGroupWithErrorMessageDemo {
readonly selected = signal<string[]>([]);
readonly isInvalid = computed(() => this.selected().length === 0);
}With List Data
F Fred
M Michael
J Jane
A Alice
B Bob
C Charlie Select team members for your project
Selected:
FFred
MMichaelimport { Component, computed, signal } from '@angular/core';
import {
AvAvatarImports,
AvDescriptionComponent,
AvLabelComponent,
AvTagGroupImports,
} from '@avesra/angular';
type User = {
id: string;
name: string;
avatar: string;
fallback: string;
};
@Component({
selector: 'app-tag-group-with-list-data-demo',
imports: [
AvTagGroupImports,
AvLabelComponent,
AvDescriptionComponent,
AvAvatarImports,
],
template: `
<av-tag-group
selection-mode="multiple"
allows-removing
[(selectedKeys)]="selected"
(remove)="onRemove($event)"
>
<label av-label>Team Members</label>
<div av-tag-group-list>
@for (user of users(); track user.id) {
<div av-tag [value]="user.id" [text-value]="user.name">
<span class="size-4" av-avatar>
<img av-avatar-image [src]="user.avatar" [alt]="user.name" />
<span av-avatar-fallback>{{ user.fallback }}</span>
</span>
{{ user.name }}
</div>
} @empty {
<p class="p-1 text-sm text-muted">No team members</p>
}
</div>
<p av-description>Select team members for your project</p>
</av-tag-group>
@if (selectedUsers().length > 0) {
<div class="mt-4 flex flex-col gap-2">
<p class="text-sm font-medium text-muted">Selected:</p>
<div class="flex flex-wrap gap-2">
@for (user of selectedUsers(); track user.id) {
<div class="flex items-center gap-2 rounded-lg bg-surface-tertiary px-2 py-1">
<span class="size-4" av-avatar>
<img av-avatar-image [src]="user.avatar" [alt]="user.name" />
<span av-avatar-fallback>{{ user.fallback }}</span>
</span>
<span class="text-sm">{{ user.name }}</span>
</div>
}
</div>
</div>
}
`,
})
export class TagGroupWithListDataDemo {
readonly users = signal<User[]>([/* … */]);
readonly selected = signal<string[]>(['fred', 'michael']);
readonly selectedUsers = computed(() =>
this.users().filter((user) => this.selected().includes(user.id)),
);
onRemove(keys: string[]): void {
this.users.update((items) => items.filter((user) => !keys.includes(user.id)));
this.selected.update((keysSelected) => keysSelected.filter((key) => !keys.includes(key)));
}
}With Prefix
Tags with icons
F Fred
M Michael
J Jane Tags with avatars
import { Component } from '@angular/core';
import {
AvAvatarImports,
AvDescriptionComponent,
AvLabelComponent,
AvTagGroupImports,
} from '@avesra/angular';
import { AppIconComponent } from '../../components/app-icon/app-icon.component';
@Component({
selector: 'app-tag-group-with-prefix-demo',
imports: [
AvTagGroupImports,
AvLabelComponent,
AvDescriptionComponent,
AvAvatarImports,
AppIconComponent,
],
template: `
<div class="flex flex-col gap-8">
<av-tag-group selection-mode="single">
<label av-label>With Icons</label>
<div av-tag-group-list>
<div av-tag value="news">
<app-icon icon="solar:document-text-linear" size="12" />
News
</div>
<div av-tag value="travel">
<app-icon icon="solar:global-linear" size="12" />
Travel
</div>
<div av-tag value="gaming">
<app-icon icon="solar:joystick-linear" size="12" />
Gaming
</div>
<div av-tag value="shopping">
<app-icon icon="solar:bag-linear" size="12" />
Shopping
</div>
</div>
<p av-description>Tags with icons</p>
</av-tag-group>
<av-tag-group selection-mode="single">
<label av-label>With Avatars</label>
<div av-tag-group-list>
<div av-tag value="fred">
<span class="size-4" av-avatar>
<img
av-avatar-image
src="/images/gradients/gradient-blue-cyan.png"
alt="Fred"
/>
<span av-avatar-fallback>F</span>
</span>
Fred
</div>
<div av-tag value="michael">
<span class="size-4" av-avatar>
<img
av-avatar-image
src="/images/gradients/gradient-cyan-blue-purple.png"
alt="Michael"
/>
<span av-avatar-fallback>M</span>
</span>
Michael
</div>
<div av-tag value="jane">
<span class="size-4" av-avatar>
<img
av-avatar-image
src="/images/gradients/gradient-purple-violet.png"
alt="Jane"
/>
<span av-avatar-fallback>J</span>
</span>
Jane
</div>
</div>
<p av-description>Tags with avatars</p>
</av-tag-group>
</div>
`,
})
export class TagGroupWithPrefixDemo {}Customization
Tailwind CSS
<av-tag-group
class="**:data-[slot=tag]:rounded-full **:data-[slot=tag]:px-3"
selection-mode="single"
>
<div class="gap-2" av-tag-group-list>
<div av-tag value="news">News</div>
<div av-tag value="travel">Travel</div>
</div>
</av-tag-group>Global CSS
To customize TagGroup classes, use the @layer components directive.
@layer components {
.av-tag-group {
@apply flex flex-col gap-2;
}
.av-tag-group__list {
@apply flex flex-wrap gap-2;
}
.av-tag {
@apply rounded-full px-3 py-1;
}
.av-tag__remove-button {
@apply ms-1;
}
}Styling Reference
Avesra follows the BEM methodology so variants and states stay reusable and easy to customize.
CSS Classes
Base Classes
.av-tag-group— Base tag group container.av-tag-group__list— Container for the list of tags.av-tag— Base tag styles.av-tag__remove-button— Remove button trigger
Slot Classes
.av-tag-group [slot="description"]/[data-slot="description"]— Description slot styles.av-tag-group [slot="errorMessage"]/[data-slot="error-message"]— Error message slot styles
Size Classes
.av-tag--sm— Small size tag.av-tag--md— Medium size tag (default).av-tag--lg— Large size tag
Variant Classes
.av-tag--default— Default variant.av-tag--surface— Surface variant
State Classes
.av-tag[data-selected="true"]— Selected tag state.av-tag[data-disabled="true"]— Disabled tag state.av-tag[data-hovered="true"]— Hovered tag state.av-tag[data-pressed="true"]— Pressed tag state.av-tag[data-focus-visible="true"]— Focused tag state (keyboard focus)
Interactive States
The component supports both CSS pseudo-classes and data attributes:
- Hover:
:hoveror[data-hovered="true"]on tag - Focus:
:focus-visibleor[data-focus-visible="true"]on tag - Pressed:
:activeor[data-pressed="true"]on tag - Selected:
[data-selected="true"]or[aria-selected="true"]on tag - Disabled:
[data-disabled="true"]or[aria-disabled="true"]on tag
API Reference
TagGroup
Props for av-tag-group. div[av-tag-group-list] has no inputs.
| Prop | Type | Default | Description |
|---|---|---|---|
size | 'sm' | 'md' | 'lg' | — | Shared size for child tags. |
variant | 'default' | 'surface' | — | Shared visual variant for child tags. |
disabled | boolean | false | Disables all child tags. |
allows-removing | boolean | false | Shows remove buttons on child tags and enables remove keyboard shortcuts. |
selection-mode | 'none' | 'single' | 'multiple' | 'none' | Whether tags can be selected. |
disabled-keys | string[] | [] | Keys that cannot be selected or removed. |
default-selected-keys | string[] | [] | Initial selected keys for uncontrolled usage. |
selectedKeys | string[] | — | Selected keys. Supports two-way binding with [(selectedKeys)]. |
remove | Output<string[]> | — | Emitted with removed tag keys when a tag is removed. |
Tag
Props for div[av-tag] / span[av-tag].
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | — | Unique key for the tag (used for selection and remove). |
text-value | string | — | Accessible text representation when the tag has complex content. |
size | 'sm' | 'md' | 'lg' | — | Tag size. Inherits from `av-tag-group` when omitted. |
variant | 'default' | 'surface' | — | Visual variant. Inherits from `av-tag-group` when omitted. |
disabled | boolean | false | Whether this tag is disabled. |
Tag.RemoveButton
Props for button[av-tag-remove-button]. When allows-removing is set and no custom remove button is projected, a default button is auto-rendered.
| Prop | Type | Default | Description |
|---|---|---|---|
aria-label | string | 'Remove tag' | Accessible label for the remove action. |
disabled | boolean | false | Disables the remove button. |
useDefaultIcon | boolean | true | Renders the built-in close icon when true. |