+
+
+
+
+
+
+ {{ tag.displayName }}
+
+
+ {{ t('systemtags', 'No tags available') }}
+
+
+
+
+
diff --git a/apps/systemtags/src/files_filters/TagsFilter.ts b/apps/systemtags/src/files_filters/TagsFilter.ts
new file mode 100644
index 0000000000000..c58f36843d693
--- /dev/null
+++ b/apps/systemtags/src/files_filters/TagsFilter.ts
@@ -0,0 +1,74 @@
+/*!
+ * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+import type { IFileListFilterChip, IFileListFilterWithUi, INode } from '@nextcloud/files'
+import type { TagWithId } from '../types.ts'
+
+import svgTagOutline from '@mdi/svg/svg/tag-outline.svg?raw'
+import { FileListFilter, registerFileListFilter } from '@nextcloud/files'
+import { t } from '@nextcloud/l10n'
+import { defineCustomElement } from 'vue'
+import FileListFilterTagsCE from '../components/FileListFilterTags.vue'
+import { getNodeSystemTags } from '../utils.ts'
+
+const tagName = 'systemtags-file-list-filter-tags'
+
+class TagsFilter extends FileListFilter implements IFileListFilterWithUi {
+ #selectedTags: TagWithId[] = []
+
+ public readonly displayName = t('systemtags', 'Tags')
+ public readonly iconSvgInline = svgTagOutline
+ public readonly tagName = tagName
+
+ constructor() {
+ super('systemtags:tags', 75)
+ }
+
+ public filter(nodes: INode[]): INode[] {
+ if (this.#selectedTags.length === 0) {
+ return nodes
+ }
+
+ const selectedNames = this.#selectedTags.map((tag) => tag.displayName)
+ return nodes.filter((node) => {
+ const nodeTags = getNodeSystemTags(node)
+ return selectedNames.some((name) => nodeTags.includes(name))
+ })
+ }
+
+ public reset(): void {
+ this.dispatchEvent(new CustomEvent('reset'))
+ }
+
+ public get selectedTags(): TagWithId[] {
+ return this.#selectedTags
+ }
+
+ public setTags(tags?: TagWithId[]): void {
+ this.#selectedTags = tags ?? []
+ this.filterUpdated()
+
+ const chips: IFileListFilterChip[] = this.#selectedTags.map((tag) => ({
+ icon: svgTagOutline,
+ text: tag.displayName,
+ onclick: () => {
+ this.dispatchEvent(new CustomEvent('deselect', { detail: tag.id }))
+ this.setTags(this.#selectedTags.filter((t) => t.id !== tag.id))
+ },
+ }))
+ this.updateChips(chips)
+ }
+}
+
+export type { TagsFilter }
+
+/**
+ * Register the file list filter by system tags
+ */
+export function registerTagsFilter() {
+ const TagsFilterElement = defineCustomElement(FileListFilterTagsCE, { shadowRoot: false })
+ customElements.define(tagName, TagsFilterElement)
+ registerFileListFilter(new TagsFilter())
+}
diff --git a/apps/systemtags/src/init.ts b/apps/systemtags/src/init.ts
index c98cb3e7210af..6a5e00b201566 100644
--- a/apps/systemtags/src/init.ts
+++ b/apps/systemtags/src/init.ts
@@ -9,6 +9,7 @@ import { action as bulkSystemTagsAction } from './files_actions/bulkSystemTagsAc
import { registerFileSidebarAction } from './files_actions/filesSidebarAction.ts'
import { action as inlineSystemTagsAction } from './files_actions/inlineSystemTagsAction.ts'
import { action as openInFilesAction } from './files_actions/openInFilesAction.ts'
+import { registerTagsFilter } from './files_filters/TagsFilter.ts'
import { registerSystemTagsView } from './files_views/systemtagsView.ts'
registerDavProperty('nc:system-tags')
@@ -18,3 +19,4 @@ registerFileAction(openInFilesAction)
registerSystemTagsView()
registerFileSidebarAction()
+registerTagsFilter()