diff --git a/libs/types/src/config/globals.d.ts b/libs/types/src/config/globals.d.ts
index 03ca58f..dc2c046 100644
--- a/libs/types/src/config/globals.d.ts
+++ b/libs/types/src/config/globals.d.ts
@@ -1,4 +1,5 @@
 import type { HeaderEntry } from './navigation'
+import type { MediaEntryTag } from '../content/entryTag'
 import type { RouteCollection } from './routing'
 import type { SiteThemeList } from './themes'
 import type { WarningModal } from './warnings'
@@ -12,5 +13,6 @@ export type SiteGlobals = {
   id: string
   stylesheetUrls: string[]
   themes: SiteThemeList
+  tags?: string | MediaEntryTag[]
   warning: WarningModal
 }
diff --git a/libs/types/src/content/entryTag.d.ts b/libs/types/src/content/entryTag.d.ts
index 9b7fa7c..a17bf76 100644
--- a/libs/types/src/content/entryTag.d.ts
+++ b/libs/types/src/content/entryTag.d.ts
@@ -2,7 +2,11 @@
  * Defines a tag used by entries in a gallery-list/article-list,
  * used for filtering entries from view
  */
-export type EntryTag = {
+export type MediaEntryTag = {
+  /**
+   * specifies the id for the tag
+   */
+  tagId: string
   /**
    * specifies a category that the tag belongs to
    * in order to optionally organize them in the view;
@@ -11,19 +15,13 @@ export type EntryTag = {
    * placed before any other sections formed by categories (if any)
    */
   category?: string
+  /**
+   * can be used to describe a tag when hovering over it in the UI
+   */
+  description?: string
   /**
    * specifies the name that the tag will appear as in the DOM;
    * if not specified, the id of the tag will be used in its place
    */
   displayName?: string
 }
-
-/**
- * Defines the list of tags in a gallery-list/article-list,
- * a key-value object where the value is the entry,
- * and the key represents the id of a tag;
- * the id of a tag must be unique,
- * and the ids specified in a gallery/article entry must match
- * the ids specified in `EntryTagCollection` in order for them to work effectively
- */
-export type EntryTagCollection = { [id: string]: EntryTag }
diff --git a/libs/types/src/content/templates/shared.d.ts b/libs/types/src/content/templates/shared.d.ts
index 940a5f3..0f449c8 100644
--- a/libs/types/src/content/templates/shared.d.ts
+++ b/libs/types/src/content/templates/shared.d.ts
@@ -1,5 +1,4 @@
 import type { DateRange } from '../dateRange'
-import type { EntryTagCollection } from '../entryTag'
 
 /**
  * Defines entries that are already fetched or are embedded directly in the list.
@@ -40,22 +39,6 @@ export type ListWithEntries<T> = {
  */
 export type ResolvedListEntries<T> = ListEntries<Promise<T>>
 
-/**
- * Defines a list that supports tagging the entries
- */
-export type ListWithTags<T> = {
-  /**
-   * the tags to use for filtering entries
-   */
-  tags?: EntryTagCollection
-  /**
-   * whether or not tag filtering removes entries completely from view;
-   * if false, they will apply a class selector instead
-   * in order to manually style (CSS filtering/opacity/etc.)
-   */
-  removeFromView?: boolean
-} & ListWithEntries<T>
-
 /**
  * Defines a list that has warnings on its entries
  */
diff --git a/projects/frontend/src/components/shared/filter-panel.vue b/projects/frontend/src/components/shared/filter-panel.vue
index c8c57d0..4291c1e 100644
--- a/projects/frontend/src/components/shared/filter-panel.vue
+++ b/projects/frontend/src/components/shared/filter-panel.vue
@@ -1,27 +1,28 @@
 <script setup lang="ts">
-import { computed } from 'vue'
-import type { EntryTagCollection } from '@goldenwere/mackenzii-types'
+import { computed, ref, onMounted } from 'vue'
+import type { MediaEntryTag } from '@goldenwere/mackenzii-types'
+import { fetchAndParseConfig } from 'src/utilities/fetch'
 
 const props = defineProps<{
-  tags: EntryTagCollection
+  tags: string | MediaEntryTag[]
 }>()
 
 const emits = defineEmits<{
   (e: 'toggledTagsChanged', value: string[]): void
 }>()
 
+const tagsLoaded = ref([] as MediaEntryTag[])
 const tagsByCategory = computed(() => {
-  const value: { [category: string]: Record<string, string> } = { 'NoCategory': {}}
+  const value: { [category: string]: MediaEntryTag[] } = { 'NoCategory': []}
 
-  Object.keys(props.tags).forEach(id => {
-    const tag = props.tags![id]
+  tagsLoaded.value.forEach((tag) => {
     if (!!tag.category) {
       if (!value[tag.category]) {
-        value[tag.category] = {}
+        value[tag.category] = []
       }
-      value[tag.category][id] = tag.displayName || id
+      value[tag.category].push(tag)
     } else {
-      value['NoCategory'][id] = tag.displayName || id
+      value['NoCategory'].push(tag)
     }
   })
 
@@ -44,6 +45,7 @@ const onToggleTag = (event: Event, tagId: string) => {
       tagsToggled.splice(index, 1)
     }
   }
+  console.log(tagsToggled)
   emits('toggledTagsChanged', tagsToggled)
 }
 
@@ -53,6 +55,12 @@ const onToggleTag = (event: Event, tagId: string) => {
 const resetTags = () => {
   tagsToggled = []
 }
+
+onMounted(async () => {
+  tagsLoaded.value = Array.isArray(props.tags)
+    ? tagsLoaded.value = props.tags
+    : await fetchAndParseConfig<MediaEntryTag[]>(props.tags)
+})
 </script>
 
 <template lang="pug">
@@ -66,17 +74,17 @@ const resetTags = () => {
       v-if='category !== "NoCategory"'
     ) {{ category }}
     .input.labeled-checkbox(
-      v-for='(tagDisplayName, tagId) in tags'
-      :id='tagId'
+      v-for='tag in tags'
+      :id='tag.tagId'
     )
       label(
-        :for='`${tagId}-toggle`'
-      ) {{ tagDisplayName }}
+        :for='`${tag.tagId}-toggle`'
+      ) {{ tag.displayName || tag.tagId }}
       input(
         type='checkbox'
-        :name='`${tagId}-toggle`'
-        :id='`${tagId}-toggle`'
-        @input='onToggleTag($event, tagId)'
+        :name='`${tag.tagId}-toggle`'
+        :id='`${tag.tagId}-toggle`'
+        @input='onToggleTag($event, tag.tagId)'
       )
 </template>
 
diff --git a/projects/frontend/src/main.ts b/projects/frontend/src/main.ts
index 642b0dc..6338ca6 100644
--- a/projects/frontend/src/main.ts
+++ b/projects/frontend/src/main.ts
@@ -21,7 +21,7 @@ export const createApp = ViteSSG(
   // the root component
   main,
   // vue-router options
-  { routes: createRoutes(globals as SiteGlobals) },
+  { routes: createRoutes(globals as unknown as SiteGlobals) },
   // function to have custom setups
   async ({ app, router, routes, isClient, initialState }) => {
     const hljsResolved: HLJSApi = await hljs as any
@@ -44,6 +44,6 @@ export const createApp = ViteSSG(
     }
 
     app.use(createPinia())
-    initializeRouteStore(routes, globals as SiteGlobals)
+    initializeRouteStore(routes, globals as unknown as SiteGlobals)
   },
 )
diff --git a/projects/frontend/src/views/shared/media-list.vue b/projects/frontend/src/views/shared/media-list.vue
index 8e88552..01748d6 100644
--- a/projects/frontend/src/views/shared/media-list.vue
+++ b/projects/frontend/src/views/shared/media-list.vue
@@ -1,8 +1,7 @@
 <script setup lang="ts">
-import { computed, onMounted, ref } from 'vue'
+import { computed, onMounted, useTemplateRef, ref, toRaw } from 'vue'
 import type {
   ConfigfulRouteDefinition,
-  ListWithTags,
   ListWithWarnings,
   MediaEntry,
   ResolvedListEntries,
@@ -16,7 +15,6 @@ import GalleryTile from '../gallery/gallery-tile.vue'
 import ArticleTile from '../article/article-tile.vue'
 
 type MediaList =
-  & ListWithTags<MediaEntry>
   & ListWithWarnings<MediaEntry>
 
 /**
@@ -41,6 +39,7 @@ const viewPath = computed(() => `${currentRoute.path}/view`)
 
 const config = ref(null as MediaList | null)
 const entries = ref({} as DisplayedEntries)
+const entriesRefs = useTemplateRef('tiles' as any)
 const hasWarnings = ref(false)
 const hideWarnings = defineModel('hideWarnings', { type: Boolean })
 const template = ref(routeConfig.template)
@@ -117,8 +116,8 @@ onMounted(async () => {
         )
   Transition
     FilterPanel(
-      v-if='ready && !!config.tags'
-      :tags='config.tags'
+      v-if='ready && globalConfig.tags'
+      :tags='globalConfig.tags'
       @toggledTagsChanged='onToggledTagsChanged($event)'
     )
   Transition
@@ -130,7 +129,8 @@ onMounted(async () => {
         v-for='(entry, id) in entries'
       )
         GalleryTile(
-          v-if='template === "gallery-list" && (!entry.isHidden || !config.removeFromView)'
+          v-if='template === "gallery-list"'
+          ref='tiles'
           :class='{ hidden: entry.isHidden && !config.removeFromView }'
           :hideWarnings='hideWarnings'
           :id='id'
@@ -139,7 +139,8 @@ onMounted(async () => {
           :entry='entry'
         )
         ArticleTile(
-          v-else-if='template === "article-list" && (!entry.isHidden || !config.removeFromView)'
+          v-else-if='template === "article-list"'
+          ref='tiles'
           :class='{ hidden: entry.isHidden && !config.removeFromView }'
           :hideWarnings='hideWarnings'
           :id='id'