diff --git a/libs/ui/src/lib/tags-selector/index.ts b/libs/ui/src/lib/tags-selector/index.ts
new file mode 100644
index 000000000..360bce671
--- /dev/null
+++ b/libs/ui/src/lib/tags-selector/index.ts
@@ -0,0 +1 @@
+export * from './tags-selector.component';
diff --git a/libs/ui/src/lib/tags-selector/tags-selector.component.html b/libs/ui/src/lib/tags-selector/tags-selector.component.html
new file mode 100644
index 000000000..7643b3586
--- /dev/null
+++ b/libs/ui/src/lib/tags-selector/tags-selector.component.html
@@ -0,0 +1,32 @@
+
+ Tags
+
+ @for (tag of tagsSelected(); track tag.id) {
+
+ {{ tag.name }}
+
+
+ }
+
+
+
+ @for (tag of tagsUnselected(); track tag.id) {
+
+ {{ tag.name }}
+
+ }
+
+
diff --git a/libs/ui/src/lib/tags-selector/tags-selector.component.scss b/libs/ui/src/lib/tags-selector/tags-selector.component.scss
new file mode 100644
index 000000000..5d4e87f30
--- /dev/null
+++ b/libs/ui/src/lib/tags-selector/tags-selector.component.scss
@@ -0,0 +1,3 @@
+:host {
+ display: block;
+}
diff --git a/libs/ui/src/lib/tags-selector/tags-selector.component.ts b/libs/ui/src/lib/tags-selector/tags-selector.component.ts
new file mode 100644
index 000000000..4456c07c8
--- /dev/null
+++ b/libs/ui/src/lib/tags-selector/tags-selector.component.ts
@@ -0,0 +1,98 @@
+import { COMMA, ENTER } from '@angular/cdk/keycodes';
+import { CommonModule } from '@angular/common';
+import {
+ ChangeDetectionStrategy,
+ Component,
+ computed,
+ CUSTOM_ELEMENTS_SCHEMA,
+ effect,
+ ElementRef,
+ EventEmitter,
+ Input,
+ OnInit,
+ Output,
+ signal,
+ ViewChild
+} from '@angular/core';
+import { FormsModule } from '@angular/forms';
+import {
+ MatAutocompleteModule,
+ MatAutocompleteSelectedEvent
+} from '@angular/material/autocomplete';
+import { MatChipsModule } from '@angular/material/chips';
+import { MatFormFieldModule } from '@angular/material/form-field';
+import { MatIconModule } from '@angular/material/icon';
+import { MatInputModule } from '@angular/material/input';
+import { Tag } from '@prisma/client';
+
+@Component({
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ imports: [
+ CommonModule,
+ FormsModule,
+ MatAutocompleteModule,
+ MatFormFieldModule,
+ MatInputModule,
+ MatChipsModule,
+ MatIconModule
+ ],
+ schemas: [CUSTOM_ELEMENTS_SCHEMA],
+ selector: 'gf-tags-selector',
+ styleUrls: ['./tags-selector.component.scss'],
+ templateUrl: 'tags-selector.component.html'
+})
+export class GfTagsSelectorComponent implements OnInit {
+ @Input() tags: Tag[];
+ @Input() tagsAvailable: Tag[];
+
+ @Output() tagsChanged = new EventEmitter();
+
+ @ViewChild('tagInput') tagInput: ElementRef;
+
+ public readonly tagsSelected = signal([]);
+ public readonly separatorKeysCodes: number[] = [COMMA, ENTER];
+ public readonly tagsUnselected = computed(() => {
+ const tags = this.tagsSelected();
+ return tags ? this.filterTags(tags) : this.tagsAvailable.slice();
+ });
+
+ public constructor() {
+ effect(() => {
+ if (this.tagsSelected()) {
+ this.tagsChanged.emit(this.tagsSelected());
+ }
+ });
+ }
+
+ public ngOnInit() {
+ this.tagsSelected.set(this.tags);
+ }
+
+ public onAddTag(event: MatAutocompleteSelectedEvent) {
+ const tag = this.tagsAvailable.find(({ id }) => {
+ return id === event.option.value;
+ });
+ this.tagsSelected.update((tags) => {
+ return [...(tags ?? []), tag];
+ });
+ this.tagInput.nativeElement.value = '';
+ }
+
+ public onRemoveTag(tag: Tag) {
+ this.tagsSelected.update((tags) => {
+ return tags.filter(({ id }) => {
+ return id !== tag.id;
+ });
+ });
+ }
+
+ private filterTags(tagsSelected: Tag[]) {
+ const tagIds = tagsSelected.map(({ id }) => {
+ return id;
+ });
+
+ return this.tagsAvailable.filter(({ id }) => {
+ return !tagIds.includes(id);
+ });
+ }
+}