diff --git a/src/App.vue b/src/App.vue
index d6b5c6850bf365e03fdd5aae0bdc93b4d28ef4ec..405ef8993d04e67552ede81366ffc9f9dbdadab3 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -34,7 +34,7 @@ onBeforeMount(() => {
 
 <template>
   <NavbarTop />
-  <div class="container">
+  <div class="container mt-4">
     <router-view></router-view>
   </div>
 </template>
diff --git a/src/assets/main.css b/src/assets/main.css
index 06d798ea4ea6f5af9cf9dce8a26eaf8bbdc7ba90..7024e78b25fc6d1c60c77ff425523d2a21fca9e4 100644
--- a/src/assets/main.css
+++ b/src/assets/main.css
@@ -6,3 +6,11 @@ body {
 .top-toast {
     top: 4rem;
 }
+
+.w-fit {
+    width: fit-content;
+}
+
+.cursor-pointer {
+    cursor: pointer;
+}
diff --git a/src/components/BootstrapIcon.vue b/src/components/BootstrapIcon.vue
index 2722a662489ea097d2d2e5883f3a6c38bd4aec4a..7c57717af6ea57ba8608201fefd0bb6ebc5531f2 100644
--- a/src/components/BootstrapIcon.vue
+++ b/src/components/BootstrapIcon.vue
@@ -14,9 +14,9 @@ import iconPath from "bootstrap-icons/bootstrap-icons.svg";
 
 const props = defineProps({
   icon: { type: String, required: true },
-  width: { type: Number, default: 24, required: false },
-  height: { type: Number, default: 24, required: false },
-  fill: { type: String, default: "black", required: false },
+  width: { type: String, default: "1em", required: false },
+  height: { type: String, default: "1em", required: false },
+  fill: { type: String, default: "currentColor", required: false },
 });
 </script>
 
diff --git a/src/components/NavbarTop.vue b/src/components/NavbarTop.vue
index 81a6b2e3406253bbd54e7697caf514c460877a6d..d8fbb2f5c5818102c073affce8c418c42ddbae96 100644
--- a/src/components/NavbarTop.vue
+++ b/src/components/NavbarTop.vue
@@ -21,6 +21,9 @@ const activeRoute = ref("");
 const objectStorageActive: ComputedRef<boolean> = computed(
   () => activeRoute.value == "buckets" || activeRoute.value == "s3_keys"
 );
+const workflowActive: ComputedRef<boolean> = computed(
+  () => activeRoute.value == "workflows"
+);
 
 watch(
   () => route.name,
@@ -28,10 +31,8 @@ watch(
     if (typeof to === "string") {
       if (to.startsWith("bucket")) {
         activeRoute.value = "buckets";
-      } else if (to.startsWith("s3_keys")) {
-        activeRoute.value = "s3_keys";
       } else {
-        activeRoute.value = "";
+        activeRoute.value = to;
       }
     } else {
       activeRoute.value = "";
@@ -41,7 +42,9 @@ watch(
 </script>
 
 <template>
-  <header class="navbar navbar-expand-lg navbar-dark bd-navbar sticky-top">
+  <header
+    class="navbar navbar-expand-lg navbar-dark bd-navbar bg-dark sticky-top border-bottom border-secondary"
+  >
     <nav class="container-xxl bd-gutter flex-wrap flex-lg-nowrap text-light">
       <button
         class="navbar-toggler"
@@ -85,18 +88,12 @@ watch(
             </a>
             <ul class="dropdown-menu dropdown-menu-dark">
               <li>
-                <router-link
-                  class="dropdown-item"
-                  :to="{ name: 'buckets' }"
-                  :class="{ active: activeRoute === 'buckets' }"
+                <router-link class="dropdown-item" :to="{ name: 'buckets' }"
                   >Buckets</router-link
                 >
               </li>
               <li>
-                <router-link
-                  class="dropdown-item"
-                  :to="{ name: 's3_keys' }"
-                  :class="{ active: activeRoute === 's3_keys' }"
+                <router-link class="dropdown-item" :to="{ name: 's3_keys' }"
                   >S3 Keys</router-link
                 >
               </li>
@@ -107,6 +104,7 @@ watch(
           <li class="nav-item dropdown">
             <a
               class="nav-link dropdown-toggle"
+              :class="{ active: workflowActive }"
               href="#"
               role="button"
               data-bs-toggle="dropdown"
@@ -116,7 +114,9 @@ watch(
             </a>
             <ul class="dropdown-menu dropdown-menu-dark">
               <li>
-                <a class="dropdown-item" href="#">Workflows</a>
+                <router-link class="dropdown-item" :to="{ name: 'workflows' }"
+                  >Workflows</router-link
+                >
               </li>
               <li>
                 <a class="dropdown-item" href="#">Executions</a>
@@ -134,7 +134,7 @@ watch(
           aria-expanded="false"
         >
           <strong class="me-2">{{ store.user.display_name }}</strong>
-          <bootstrap-icon icon="person-circle" fill="white" />
+          <bootstrap-icon icon="person-circle" class="fs-4" />
         </a>
         <ul
           class="dropdown-menu dropdown-menu-dark text-small shadow"
@@ -142,7 +142,7 @@ watch(
         >
           <li><hr class="dropdown-divider" /></li>
           <li>
-            <a class="dropdown-item pseudo-link" @click="logout">Sign out</a>
+            <a class="dropdown-item cursor-pointer" @click="logout">Sign out</a>
           </li>
         </ul>
       </div>
@@ -150,8 +150,4 @@ watch(
   </header>
 </template>
 
-<style scoped>
-.pseudo-link {
-  cursor: pointer;
-}
-</style>
+<style scoped></style>
diff --git a/src/components/modals/SearchUserModal.vue b/src/components/modals/SearchUserModal.vue
index e17b35e14d8c9636c4675a8757c8ce66479e844c..658f095d05a274f202abd96deddf8952fa2f4504 100644
--- a/src/components/modals/SearchUserModal.vue
+++ b/src/components/modals/SearchUserModal.vue
@@ -82,7 +82,7 @@ function searchUser(name: string) {
     <template v-slot:body>
       <div class="input-group mt-2 mb-4">
         <span class="input-group-text" id="objects-search-wrapping"
-          ><bootstrap-icon icon="search" :width="16" :height="16"
+          ><bootstrap-icon icon="search"
         /></span>
         <input
           class="form-control"
@@ -100,10 +100,9 @@ function searchUser(name: string) {
         <bootstrap-icon
           icon="x-lg"
           class="mb-2"
-          :width="56"
-          :height="56"
+          width="56"
+          height="56"
           style="color: var(--bs-danger)"
-          fill="currentColor"
         /><br />
         <span class="text-danger"
           >There seems to be an error<br />Try again later</span
@@ -126,10 +125,9 @@ function searchUser(name: string) {
         <bootstrap-icon
           icon="search"
           class="mb-2"
-          :width="56"
-          :height="56"
+          width="56"
+          height="56"
           style="color: var(--bs-secondary)"
-          fill="currentColor"
         /><br />
         <span v-if="formState.searchString.length > 2"
           >Could not find any Users</span
diff --git a/src/components/object-storage/BucketListItem.vue b/src/components/object-storage/BucketListItem.vue
index 1412a89896a7bcc6eecccc74902062585ff48a93..bec5afd837f91d85c869443d293b6c8474a12ee1 100644
--- a/src/components/object-storage/BucketListItem.vue
+++ b/src/components/object-storage/BucketListItem.vue
@@ -92,9 +92,6 @@ onMounted(() => {
             v-if="props.active && permission == null && props.deletable"
             icon="trash-fill"
             class="delete-icon me-2"
-            :width="16"
-            :height="16"
-            fill="currentColor"
             @click="emit('delete-bucket', bucket.name)"
           />
           <bootstrap-icon
@@ -103,9 +100,6 @@ onMounted(() => {
             :data-bs-target="'#view-bucket-details-modal' + randomIDSuffix"
             v-if="props.active"
             icon="info-circle-fill"
-            :width="16"
-            :height="16"
-            fill="currentColor"
           />
         </div>
       </router-link>
diff --git a/src/components/object-storage/modals/PermissionModal.vue b/src/components/object-storage/modals/PermissionModal.vue
index ddc289951059108f1f8066d5e98e56c2aa26adcb..30048c8b331711f61bc284cc358b2dcbe2430b89 100644
--- a/src/components/object-storage/modals/PermissionModal.vue
+++ b/src/components/object-storage/modals/PermissionModal.vue
@@ -336,10 +336,7 @@ onMounted(() => {
       <bootstrap-icon
         v-if="props.deletable"
         icon="trash-fill"
-        :height="15"
-        :width="15"
-        fill="currentColor"
-        class="me-2"
+        class="me-2 cursor-pointer"
         :class="{ 'delete-icon': !formState.loading }"
         data-bs-toggle="modal"
         :data-bs-target="'#delete-permission-modal' + randomIDSuffix"
@@ -347,10 +344,7 @@ onMounted(() => {
       <bootstrap-icon
         v-if="formState.readonly && props.editable"
         icon="pencil-fill"
-        :height="15"
-        :width="15"
-        fill="currentColor"
-        class="pseudo-link"
+        class="pseudo-link cursor-pointer"
         @click="formState.readonly = false"
       />
     </template>
@@ -401,12 +395,7 @@ onMounted(() => {
               data-bs-toggle="modal"
               :data-bs-target="'#search-user-modal' + randomIDSuffix"
             >
-              <bootstrap-icon
-                icon="search"
-                :height="13"
-                :width="13"
-                fill="white"
-              />
+              <bootstrap-icon icon="search" />
             </button>
           </div>
         </div>
@@ -510,12 +499,7 @@ onMounted(() => {
               @click="permission.file_prefix = undefined"
               :hidden="permission.file_prefix == undefined"
             >
-              <bootstrap-icon
-                icon="x-lg"
-                :height="14"
-                :width="14"
-                fill="currentColor"
-              />
+              <bootstrap-icon icon="x-lg" />
             </button>
           </div>
         </div>
@@ -563,7 +547,6 @@ onMounted(() => {
 
 <style scoped>
 .pseudo-link {
-  cursor: pointer;
   color: var(--bs-secondary);
 }
 .pseudo-link:hover {
@@ -572,7 +555,6 @@ onMounted(() => {
 
 .delete-icon {
   color: var(--bs-secondary);
-  cursor: pointer;
 }
 .delete-icon:hover {
   color: var(--bs-danger);
diff --git a/src/components/workflows/WorkflowCard.vue b/src/components/workflows/WorkflowCard.vue
new file mode 100644
index 0000000000000000000000000000000000000000..42e31f131eb0547f04c642b08bfac8f229778c5d
--- /dev/null
+++ b/src/components/workflows/WorkflowCard.vue
@@ -0,0 +1,98 @@
+<script setup lang="ts">
+import type { WorkflowOut, WorkflowVersionReduced } from "@/client/workflow";
+import BootstrapIcon from "@/components/BootstrapIcon.vue";
+import dayjs from "dayjs";
+import { onMounted, ref, computed } from "vue";
+import type { Ref, ComputedRef } from "vue";
+import { Tooltip } from "bootstrap";
+import { useWorkflowStore } from "@/stores/workflows";
+
+const props = defineProps<{
+  workflow: WorkflowOut;
+  loading: boolean;
+}>();
+const workflowRepository = useWorkflowStore();
+
+const randomIDSuffix: string = Math.random().toString(16).substr(2, 8);
+const truncateDescription: Ref<boolean> = ref(true);
+const latestVersion: ComputedRef<WorkflowVersionReduced | undefined> = computed(
+  () =>
+    props.loading
+      ? undefined
+      : workflowRepository.latestVersion(props.workflow.workflow_id)
+);
+
+onMounted(() => {
+  if (!props.loading) {
+    new Tooltip("#creationDate-" + randomIDSuffix);
+  }
+});
+</script>
+
+<template>
+  <div class="card-hover border border-secondary card text-bg-dark m-2">
+    <div class="card-body">
+      <h3 class="card-title">
+        <div v-if="props.loading" class="placeholder-glow">
+          <span class="placeholder col-6"></span>
+        </div>
+        <a v-else href="#">{{ props.workflow.name }}</a>
+      </h3>
+      <p class="card-text" :class="{ 'text-truncate': truncateDescription }">
+        <span v-if="props.loading" class="placeholder-glow"
+          ><span class="placeholder col-12"></span
+        ></span>
+        <span
+          v-else
+          @click="truncateDescription = false"
+          :class="{
+            'cursor-pointer': truncateDescription,
+          }"
+          >{{ props.workflow.short_description }}</span
+        >
+      </p>
+      <div class="d-flex justify-content-between mb-0">
+        <div v-if="props.loading" class="placeholder-glow w-50">
+          <span class="placeholder placeholder-lg w-50 bg-success"></span>
+        </div>
+        <a
+          v-else
+          :href="
+            workflow.repository_url + '/tree/' + latestVersion?.git_commit_hash
+          "
+          target="_blank"
+          class="btn btn-outline-success"
+          role="button"
+        >
+          <bootstrap-icon class="fs-5" icon="tag-fill" />
+          {{ latestVersion?.version }}
+        </a>
+        <div v-if="props.loading" class="placeholder-glow w-25">
+          <span class="placeholder w-100 bg-secondary"></span>
+        </div>
+        <span
+          v-else
+          :id="'creationDate-' + randomIDSuffix"
+          data-bs-toggle="tooltip"
+          class="align-self-end text-secondary"
+          :data-bs-title="
+            dayjs(workflow.versions[0].created_at).format('DD.MM.YYYY HH:mm:ss')
+          "
+        >
+          {{ dayjs(latestVersion?.created_at).fromNow() }}
+        </span>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style scoped>
+.card-hover {
+  transition: transform 0.3s ease-out;
+  width: 48%;
+}
+
+.card-hover:hover {
+  transform: translate(0, -5px);
+}
+</style>
diff --git a/src/router/index.ts b/src/router/index.ts
index 9dbda65734c162aff7ae181d09b704a5ba1bbd3d..39acd6ecddd838a0a4346340f3dba5d803fe3db6 100644
--- a/src/router/index.ts
+++ b/src/router/index.ts
@@ -18,8 +18,7 @@ const router = createRouter({
             {
               path: ":bucketName/:subFolders*",
               name: "bucket",
-              component: () =>
-                import("../components/object-storage/BucketView.vue"),
+              component: () => import("../views/object-storage/BucketView.vue"),
               props: true,
             },
           ],
@@ -29,6 +28,11 @@ const router = createRouter({
           name: "s3_keys",
           component: () => import("../views/object-storage/S3KeysView.vue"),
         },
+        {
+          path: "workflows",
+          name: "workflows",
+          component: () => import("../views/workflows/ListWorkflowsView.vue"),
+        },
       ],
     },
     {
diff --git a/src/stores/workflows.ts b/src/stores/workflows.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a15e6c14efe1e0b81d4996d5876eba142fe42c15
--- /dev/null
+++ b/src/stores/workflows.ts
@@ -0,0 +1,47 @@
+import { defineStore } from "pinia";
+import { WorkflowService } from "@/client/workflow";
+import type { WorkflowVersionReduced } from "@/client/workflow";
+import type { WorkflowOut } from "@/client/workflow";
+
+export const useWorkflowStore = defineStore({
+  id: "workflows",
+  state: () =>
+    ({
+      workflows: [],
+    } as {
+      workflows: WorkflowOut[];
+    }),
+  getters: {
+    latestVersion(): (
+      workflowId: string
+    ) => WorkflowVersionReduced | undefined {
+      return (workflowId) => {
+        const workflow = this.workflows.find(
+          (w) => workflowId == w.workflow_id
+        );
+        return workflow?.versions[
+          Math.max(workflow?.versions?.length - 1 ?? 0, 0)
+        ];
+      };
+    },
+  },
+  actions: {
+    fetchWorkflows(
+      onFulfilled:
+        | ((workflows: WorkflowOut[]) => void)
+        | null
+        | undefined = null,
+      // eslint-disable-next-line @typescript-eslint/no-explicit-any
+      onRejected: ((reason: any) => void) | null | undefined = null,
+      onFinally: (() => void) | null | undefined = null
+    ) {
+      WorkflowService.workflowListWorkflows()
+        .then((workflows) => {
+          this.workflows = workflows;
+          onFulfilled?.(workflows);
+        })
+        .catch(onRejected)
+        .finally(onFinally);
+    },
+  },
+});
diff --git a/src/components/object-storage/BucketView.vue b/src/views/object-storage/BucketView.vue
similarity index 95%
rename from src/components/object-storage/BucketView.vue
rename to src/views/object-storage/BucketView.vue
index 86e21c8a16c19abc7081ef8e44b42a98d1c4d62d..7bd9e5e42fd2396d4c1144cb9aad2f726b5ebbc9 100644
--- a/src/components/object-storage/BucketView.vue
+++ b/src/views/object-storage/BucketView.vue
@@ -580,7 +580,7 @@ watch(
     <div class="col-8">
       <div class="input-group mt-2">
         <span class="input-group-text" id="objects-search-wrapping"
-          ><bootstrap-icon icon="search" :width="16" :height="16"
+          ><bootstrap-icon icon="search"
         /></span>
         <input
           type="text"
@@ -603,7 +603,7 @@ watch(
         data-bs-title="Upload Object"
         data-bs-target="#upload-object-modal"
       >
-        <bootstrap-icon icon="upload" :width="16" :height="16" fill="white" />
+        <bootstrap-icon icon="upload" fill="white" />
         <span class="visually-hidden">Upload Object</span>
       </button>
       <upload-object-modal
@@ -623,7 +623,7 @@ watch(
         data-bs-title="Create Folder"
         data-bs-target="#create-folder-modal"
       >
-        <bootstrap-icon icon="plus-lg" :width="16" :height="16" fill="white" />
+        <bootstrap-icon icon="plus-lg" />
         Folder
         <span class="visually-hidden">Add Folder</span>
       </button>
@@ -645,12 +645,7 @@ watch(
         data-bs-title="Create Bucket Permission"
         data-bs-target="#create-permission-modal"
       >
-        <bootstrap-icon
-          icon="person-plus-fill"
-          :width="16"
-          :height="16"
-          fill="white"
-        />
+        <bootstrap-icon icon="person-plus-fill" />
         <span class="visually-hidden">Add Bucket Permission</span>
       </button>
       <permission-modal
@@ -677,12 +672,7 @@ watch(
         data-bs-toggle="modal"
         data-bs-target="#permission-list-modal"
       >
-        <bootstrap-icon
-          icon="person-lines-fill"
-          :width="16"
-          :height="16"
-          fill="white"
-        />
+        <bootstrap-icon icon="person-lines-fill" />
         <span class="visually-hidden">View Bucket Permissions</span>
       </button>
       <permission-list-modal
@@ -704,10 +694,9 @@ watch(
       <bootstrap-icon
         icon="search"
         class="mb-3"
-        :width="64"
-        :height="64"
+        width="64"
+        height="64"
         style="color: var(--bs-secondary)"
-        fill="currentColor"
       />
       <p>
         Bucket <i>{{ props.bucketName }}</i> not found
@@ -721,10 +710,9 @@ watch(
       <bootstrap-icon
         icon="folder-x"
         class="mb-3"
-        :width="64"
-        :height="64"
+        width="64"
+        height="64"
         style="color: var(--bs-secondary)"
-        fill="currentColor"
       />
       <p>You don't have permission for this bucket</p>
     </div>
@@ -869,13 +857,7 @@ watch(
                       data-bs-target="#delete-object-modal"
                       :disabled="!writableBucket"
                     >
-                      <bootstrap-icon
-                        icon="trash-fill"
-                        class="text-danger"
-                        :width="13"
-                        :height="13"
-                        fill="currentColor"
-                      />
+                      <bootstrap-icon icon="trash-fill" />
                       <span class="ms-1">Delete</span>
                     </button>
                   </li>
@@ -885,7 +867,7 @@ watch(
               <div v-else>
                 <button
                   type="button"
-                  class="btn btn-danger btn-sm align-middle"
+                  class="btn btn-danger btn-sm align-baseline"
                   :disabled="!writableBucket"
                   data-bs-toggle="modal"
                   data-bs-target="#delete-object-modal"
@@ -895,13 +877,7 @@ watch(
                     )
                   "
                 >
-                  <bootstrap-icon
-                    icon="trash-fill"
-                    class="text-danger me-2"
-                    :width="12"
-                    :height="12"
-                    fill="white"
-                  />
+                  <bootstrap-icon icon="trash-fill" class="me-2" />
                   <span>Delete</span>
                 </button>
               </div>
diff --git a/src/views/object-storage/BucketsView.vue b/src/views/object-storage/BucketsView.vue
index 877761c706508cf06b32dee924f84252820e29b0..15fc3872513acb8065021a38bef24fa5abab7de4 100644
--- a/src/views/object-storage/BucketsView.vue
+++ b/src/views/object-storage/BucketsView.vue
@@ -71,7 +71,7 @@ onMounted(() => {
     modalID="create-bucket-modal"
     v-if="!authStore.foreignUser"
   />
-  <div class="row m-2 border-bottom border-light mt-4">
+  <div class="row m-2 border-bottom border-light">
     <div class="col-12"></div>
     <h1 class="mb-2 text-light">Buckets</h1>
   </div>
@@ -83,7 +83,7 @@ onMounted(() => {
           class="btn btn-light"
           @click.stop.prevent="fetchBuckets"
         >
-          <bootstrap-icon icon="arrow-clockwise" />
+          <bootstrap-icon icon="arrow-clockwise" class="fs-5" />
           <span class="visually-hidden">Refresh Buckets</span>
         </button>
         <button
@@ -93,13 +93,13 @@ onMounted(() => {
           data-bs-toggle="modal"
           data-bs-target="#create-bucket-modal"
         >
-          <bootstrap-icon icon="plus-lg" />
+          <bootstrap-icon icon="plus-lg" class="fs-5" />
           <span class="visually-hidden">Create Bucket</span>
         </button>
       </div>
       <div class="input-group flex-nowrap mt-2">
         <span class="input-group-text" id="buckets-search-wrapping"
-          ><bootstrap-icon icon="search" :width="16" :height="16"
+          ><bootstrap-icon icon="search"
         /></span>
         <input
           type="text"
@@ -131,10 +131,9 @@ onMounted(() => {
             <bootstrap-icon
               icon="search"
               class="mb-2"
-              :width="56"
-              :height="56"
+              width="56"
+              height="56"
               style="color: var(--bs-secondary)"
-              fill="currentColor"
             />
             <br />
             Could not find any Buckets
@@ -145,8 +144,7 @@ onMounted(() => {
             v-for="n in 5"
             :key="n"
             :active="false"
-            :loading="true"
-            :permission="undefined"
+            loading
             :deletable="false"
             :bucket="{
               name: '',
@@ -169,10 +167,9 @@ onMounted(() => {
         <bootstrap-icon
           icon="hand-index-thumb-fill"
           class="mb-5"
-          :width="64"
-          :height="64"
+          width="64"
+          height="64"
           style="color: var(--bs-secondary)"
-          fill="currentColor"
         />
         <p>Click on a bucket to browse its content</p>
       </div>
diff --git a/src/components/object-storage/S3KeyView.vue b/src/views/object-storage/S3KeyView.vue
similarity index 97%
rename from src/components/object-storage/S3KeyView.vue
rename to src/views/object-storage/S3KeyView.vue
index 1f29e7ed3c85e875b82fb621c45e251f382e56ba..79853162da1bbd0ed5e799f2bf37e179ee83d3ed 100644
--- a/src/components/object-storage/S3KeyView.vue
+++ b/src/views/object-storage/S3KeyView.vue
@@ -60,9 +60,7 @@ function deleteKeyTrigger() {
       @click="visibleSecret = !visibleSecret"
     >
       <bootstrap-icon
-        :width="18"
-        :height="18"
-        fill="white"
+        class="fs-5"
         :icon="visibleSecret ? 'eye' : 'eye-slash'"
       />
     </button>
diff --git a/src/views/object-storage/S3KeysView.vue b/src/views/object-storage/S3KeysView.vue
index d234f3294416817478138ce48fa58c15da6c3094..42766a2e6c7c74142d5cfd5776a41aee760233e6 100644
--- a/src/views/object-storage/S3KeysView.vue
+++ b/src/views/object-storage/S3KeysView.vue
@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import S3KeyView from "@/components/object-storage/S3KeyView.vue";
+import S3KeyView from "@/views/object-storage/S3KeyView.vue";
 import BootstrapIcon from "@/components/BootstrapIcon.vue";
 import { reactive, onMounted, computed } from "vue";
 import type { ComputedRef } from "vue";
@@ -118,11 +118,11 @@ onMounted(() => {
           class="btn btn-light"
           @click="refreshKeys(authStore.user?.uid ?? 'impossible')"
         >
-          <bootstrap-icon icon="arrow-clockwise" />
+          <bootstrap-icon icon="arrow-clockwise" class="fs-5" />
           <span class="visually-hidden">Refresh S3 Keys</span>
         </button>
         <button type="button" class="btn btn-light" @click="createKey">
-          <bootstrap-icon icon="plus-lg" />
+          <bootstrap-icon icon="plus-lg" class="fs-5" />
           <span class="visually-hidden">Create S3 Key</span>
         </button>
       </div>
diff --git a/src/views/workflows/ListWorkflowsView.vue b/src/views/workflows/ListWorkflowsView.vue
new file mode 100644
index 0000000000000000000000000000000000000000..211b92019f525f93620bb5137f834fb579b2cf3b
--- /dev/null
+++ b/src/views/workflows/ListWorkflowsView.vue
@@ -0,0 +1,189 @@
+<script setup lang="ts">
+import { computed, onMounted, reactive } from "vue";
+import type { ComputedRef } from "vue";
+import { useWorkflowStore } from "@/stores/workflows";
+import type { WorkflowOut } from "@/client/workflow";
+import WorkflowCard from "@/components/workflows/WorkflowCard.vue";
+import dayjs from "dayjs";
+import BootstrapIcon from "@/components/BootstrapIcon.vue";
+
+const workflowRepository = useWorkflowStore();
+
+const workflowsState = reactive({
+  loading: true,
+  filterString: "",
+  sortByAttribute: "name",
+  sortDesc: true,
+} as {
+  loading: boolean;
+  filterString: string;
+  sortByAttribute: string;
+  sortDesc: boolean;
+});
+
+const bla: Record<string, (a: WorkflowOut, b: WorkflowOut) => boolean> = {
+  name: (a: WorkflowOut, b: WorkflowOut) =>
+    workflowsState.sortDesc ? a.name > b.name : a.name < b.name,
+  release: (a: WorkflowOut, b: WorkflowOut) => {
+    const a_date = dayjs(a.versions[a.versions.length - 1].created_at);
+    const b_date = dayjs(b.versions[b.versions.length - 1].created_at);
+    return workflowsState.sortDesc
+      ? a_date.isBefore(b_date)
+      : a_date.isAfter(b_date);
+  },
+};
+
+function filterWorkflowByString(workflow: WorkflowOut): boolean {
+  return workflowsState.filterString.length > 0
+    ? workflow.name.includes(workflowsState.filterString)
+    : true;
+}
+
+function filterWorkflowWithoutVersion(workflow: WorkflowOut): boolean {
+  return workflow.versions.length > 0;
+}
+
+const processedWorkflows: ComputedRef<WorkflowOut[]> = computed(() => {
+  return workflowRepository.workflows
+    .filter(
+      (workflow) =>
+        filterWorkflowByString(workflow) &&
+        filterWorkflowWithoutVersion(workflow)
+    )
+    .sort((a, b) => (bla[workflowsState.sortByAttribute](a, b) ? 1 : -1));
+});
+
+onMounted(() => {
+  workflowRepository.fetchWorkflows(
+    null,
+    null,
+    () => (workflowsState.loading = false)
+  );
+});
+</script>
+
+<template>
+  <div class="row m-2 border-bottom border-light mb-4">
+    <div class="col-12"></div>
+    <h1 class="mb-2 text-light">Workflows</h1>
+  </div>
+  <div class="d-flex m-2 mb-3 align-items-center justify-content-between">
+    <div class="col-5 me-auto">
+      <div class="input-group">
+        <span class="input-group-text" id="workflows-search-wrapping"
+          ><bootstrap-icon icon="search"
+        /></span>
+        <input
+          type="text"
+          class="form-control"
+          placeholder="Filter Workflows"
+          aria-label="Filter Workflows"
+          aria-describedby="workflows-search-wrapping"
+          :disabled="workflowsState.loading"
+          v-model.trim="workflowsState.filterString"
+          maxlength="20"
+        />
+      </div>
+    </div>
+    <span class="fs-5 me-3">Sort By</span>
+    <div
+      class="btn-group btn-group-sm w-fit"
+      role="group"
+      aria-label="Basic radio toggle button group"
+    >
+      <input
+        type="radio"
+        class="btn-check"
+        name="btnradio"
+        id="sortName"
+        autocomplete="off"
+        checked
+        v-model="workflowsState.sortByAttribute"
+        value="name"
+      />
+      <label class="btn btn-outline-secondary" for="sortName"
+        >Alphabetical</label
+      >
+
+      <input
+        type="radio"
+        class="btn-check"
+        name="btnradio"
+        id="sortLatestRelease"
+        autocomplete="off"
+        v-model="workflowsState.sortByAttribute"
+        value="release"
+      />
+      <label class="btn btn-outline-secondary" for="sortLatestRelease"
+        >Latest Release</label
+      >
+    </div>
+    <bootstrap-icon
+      :icon="workflowsState.sortDesc ? 'sort-down' : 'sort-up'"
+      @click="workflowsState.sortDesc = !workflowsState.sortDesc"
+      class="fs-4 ms-3 cursor-pointer"
+    />
+  </div>
+  <div v-if="!workflowsState.loading">
+    <div
+      v-if="workflowRepository.workflows.length === 0"
+      class="text-center fs-2 mt-5"
+    >
+      <bootstrap-icon
+        icon="x-lg"
+        class="mb-5"
+        width="75"
+        height="75"
+        style="color: var(--bs-secondary)"
+      />
+      <br />
+      There are no workflows in the system. Please come again later.
+    </div>
+    <div
+      v-else-if="processedWorkflows.length === 0"
+      class="text-center fs-2 mt-5"
+    >
+      <bootstrap-icon
+        icon="search"
+        class="mb-5"
+        width="75"
+        height="75"
+        style="color: var(--bs-secondary)"
+      />
+      <br />
+      Could not find any Workflows containing<br />'{{
+        workflowsState.filterString
+      }}'
+    </div>
+    <div
+      v-else
+      class="d-flex flex-wrap align-items-center justify-content-between"
+    >
+      <workflow-card
+        v-for="workflow in processedWorkflows"
+        :key="workflow.workflow_id"
+        :workflow="workflow"
+        :loading="false"
+      />
+    </div>
+  </div>
+  <div
+    v-else
+    class="d-flex flex-wrap align-items-center justify-content-between"
+  >
+    <workflow-card
+      v-for="workflow in 4"
+      :key="workflow"
+      :workflow="{
+        name: '',
+        short_description: '',
+        repository_url: '',
+        workflow_id: '',
+        versions: [],
+      }"
+      loading
+    />
+  </div>
+</template>
+
+<style scoped></style>