diff --git a/public/assets/css/styles.css b/public/assets/css/styles.css
index 6409912d04b48489bdce6d58aee3d0414d54513b..fd395eb49572759ad661bc5ad1cd5c89c7f8d6c7 100644
--- a/public/assets/css/styles.css
+++ b/public/assets/css/styles.css
@@ -1,3 +1,23 @@
 @import "bootstrap.min.css";
 
 /* TODO add sass file to import bootstrap and allow variable overrides */
+
+#eiffelRequirementsListWrapper {
+    max-height: 50rem;
+    overflow-y: scroll;
+}
+
+.eiffel-requirements-list-item {
+    border-radius: var(--bs-border-radius);
+    background-color: rgba(var(--bs-light-rgb), 1);
+    padding: 0.5rem;
+    margin-bottom: 0.5rem;
+    margin-top: 0.5rem;
+    border: 1px solid var(--bs-light-border-subtle);
+    cursor: pointer;
+    transition: all 200ms ease-in-out;
+}
+
+.eiffel-requirements-list-item:hover {
+    background-color: rgba(var(--bs-light-rgb), 0.1);
+}
\ No newline at end of file
diff --git a/public/assets/js/eiffel.js b/public/assets/js/eiffel.js
index 7500a7ff11ee1899d106dee54bf4fd9562f6be20..e1626a59f6adbfa39585da702af0de0f406c5099 100644
--- a/public/assets/js/eiffel.js
+++ b/public/assets/js/eiffel.js
@@ -1,9 +1,21 @@
+const EiffelMaxRequirementsInLocalStorage = 420;
+
 document.addEventListener('DOMContentLoaded', registerDynamicFocuses);
 document.addEventListener('htmx:afterSettle', registerDynamicFocuses);
 
+document.addEventListener('DOMContentLoaded', initRequirementsList);
+document.addEventListener('htmx:afterSettle', initRequirementsList);
+
 document.addEventListener('DOMContentLoaded', autoResizeInput);
 document.addEventListener('htmx:afterSettle', autoResizeInput);
 
+document.addEventListener('DOMContentLoaded', registerOutputEmptyBtn);
+document.addEventListener('htmx:afterSettle', registerOutputEmptyBtn);
+
+document.addEventListener('htmx:afterRequest', requirementParsed);
+document.addEventListener('newRequirementEvent', newRequirement);
+document.addEventListener('emptyRequirementsEvent', emptyRequirements);
+
 registerFocuses();
 
 registerShortcuts();
@@ -194,6 +206,29 @@ function copyRequirementToClipboard() {
         });
 }
 
+function registerOutputEmptyBtn() {
+    const outputEmptyBtn = document.getElementById('eiffelRequirementsEmpty');
+    if (!outputEmptyBtn || outputEmptyBtn.dataset.eiffelStatus === 'setup') return;
+
+    outputEmptyBtn.addEventListener('click', function () {
+        document.dispatchEvent(new CustomEvent('emptyRequirementsEvent'));
+    });
+
+    outputEmptyBtn.dataset.eiffelStatus = 'setup';
+}
+
+function copyOutputToClipboard(event) {
+    const target = event.target;
+    if (!target) return;
+
+    const output = target.innerText;
+
+    return navigator.clipboard.writeText(output)
+        .catch(() => {
+            alert('Sorry, your browser blocked copying the output to the clipboard. Try to copy manually.');
+        });
+}
+
 function clearElicitationForm() {
     const elicitationForm = document.getElementById('eiffelElicitationForm');
     if (!elicitationForm) return;
@@ -226,4 +261,154 @@ function autoResizeInput() {
 
         input.dataset.eiffelStatus = 'setup';
     });
+}
+
+function requirementParsed(event) {
+    const xhr = event.detail.xhr;
+    if (!xhr) return;
+
+    const responseHeaders = xhr.getResponseHeader('ParsingSuccessEvent');
+    if (!responseHeaders) return;
+
+    // base64 decode the response header and parse it as JSON
+    event = JSON.parse(new TextDecoder().decode(base64ToBytes(responseHeaders)));
+    if (!event) return;
+
+    const parsingSuccessEvent = event.parsingSuccessEvent;
+    if (!parsingSuccessEvent) return;
+
+    const requirement = parsingSuccessEvent.requirement;
+    if (!requirement) return;
+
+    const timestamp = Date.now();
+    let key = `eiffel-requirement-${timestamp}`;
+    localStorage.setItem(key, requirement);
+
+    document.dispatchEvent(new CustomEvent('newRequirementEvent', {
+        detail: {
+            requirement: requirement,
+            key: key
+        }
+    }));
+}
+
+function newRequirement(event) {
+    const requirement = event.detail.requirement;
+    const key = event.detail.key;
+    if (!requirement) return;
+
+    const requirementList = document.querySelector('#eiffelRequirementsListWrapper ul');
+    if (!requirementList) return;
+
+    const firstListItem = requirementList.querySelector('ul > li.eiffel-requirements-list-item')
+    if (!firstListItem) return;
+
+    const newListItem = firstListItem.cloneNode(true);
+    newListItem.innerText = requirement;
+    newListItem.dataset.eiffelRequirementKey = key;
+    newListItem.addEventListener('click', copyOutputToClipboard);
+    requirementList.prepend(newListItem);
+
+    if (!firstListItem.dataset.eiffelRequirementKey) {
+        firstListItem.classList.add('d-none');
+    }
+}
+
+function initRequirementsList() {
+    const requirementListWrapper = document.getElementById('eiffelRequirementsListWrapper');
+    if (!requirementListWrapper || requirementListWrapper.dataset.eiffelStatus === 'setup') return;
+
+    let items = {};
+
+    // get all items from local storage
+    for (let i = 0; i < localStorage.length; i++) {
+        const key = localStorage.key(i);
+        if (!key.startsWith('eiffel-requirement-')) continue;
+
+        const requirement = localStorage.getItem(key);
+        if (!requirement) continue;
+
+        items[key] = requirement;
+    }
+
+    // sort items ascending by timestamp (from key)
+    const sortedKeys = Object.keys(items).sort((a, b) => {
+        const aTimestamp = parseInt(a.replace('eiffel-requirement-', ''));
+        const bTimestamp = parseInt(b.replace('eiffel-requirement-', ''));
+
+        return aTimestamp - bTimestamp;
+    });
+    const sortedItems = {};
+    sortedKeys.forEach(key => {
+        sortedItems[key] = items[key];
+    });
+    items = sortedItems;
+
+    // clean up old items
+    items = cleanRequirementsList(items, EiffelMaxRequirementsInLocalStorage);
+
+    // call newRequirement for each item
+    Object.keys(items).forEach(key => {
+        document.dispatchEvent(new CustomEvent('newRequirementEvent', {
+            detail: {
+                requirement: items[key],
+                key: key
+            }
+        }));
+    });
+
+    requirementListWrapper.dataset.eiffelStatus = 'setup';
+}
+
+// cleanup oldest items if there are more than max items
+// expects items to be an object with key => value pairs that is sorted ascending by timestamp (from key)
+// returns the cleaned items object that was passed in
+function cleanRequirementsList(items, max) {
+    const keys = Object.keys(items);
+    // return early if there are less than max items
+    if (keys.length <= max) return items;
+
+    // delete the oldest items
+    const keysToDelete = keys.slice(0, keys.length - max);
+    keysToDelete.forEach(key => {
+        localStorage.removeItem(key);
+    });
+    console.info(`Removed ${keysToDelete.length} requirements from local storage.`);
+
+    // remove the deleted items from the list
+    keysToDelete.forEach(key => {
+        delete items[key];
+    });
+
+    return items;
+}
+
+function emptyRequirements() {
+    const requirementList = document.querySelector('#eiffelRequirementsListWrapper ul');
+    if (!requirementList) return;
+
+    const itemNodes = requirementList.querySelectorAll('li.eiffel-requirements-list-item');
+    if (!itemNodes) return;
+
+    const items = {};
+    itemNodes.forEach(itemNode => {
+        if (!itemNode.dataset.eiffelRequirementKey) return;
+        items[itemNode.dataset.eiffelRequirementKey] = itemNode.innerText;
+    });
+
+    cleanRequirementsList(items, 0);
+
+    itemNodes.forEach((itemNode) => {
+        if (!itemNode.dataset.eiffelRequirementKey) {
+            itemNode.classList.remove('d-none');
+            return;
+        }
+
+        itemNode.remove();
+    });
+}
+
+function base64ToBytes(base64) {
+    const binString = atob(base64);
+    return Uint8Array.from(binString, (m) => m.codePointAt(0));
 }
\ No newline at end of file
diff --git a/src/app/eiffel/web.go b/src/app/eiffel/web.go
index 17e3e0814ffc83e6472fc1416833e2705e6df080..fba9462b85e6c3eba2b1c4bc9147547b40217727 100644
--- a/src/app/eiffel/web.go
+++ b/src/app/eiffel/web.go
@@ -1,6 +1,7 @@
 package eiffel
 
 import (
+	"encoding/base64"
 	"encoding/json"
 	"errors"
 	"fmt"
@@ -15,7 +16,6 @@ import (
 	"github.com/org-harmony/harmony/src/core/util"
 	"github.com/org-harmony/harmony/src/core/web"
 	"net/http"
-	"path/filepath"
 	"strings"
 )
 
@@ -35,10 +35,6 @@ var (
 	ErrTemplateNotFound = errors.New("eiffel.elicitation.template.not-found")
 	// ErrTemplateVariantNotFound will be displayed to the user if the template variant could not be found.
 	ErrTemplateVariantNotFound = errors.New("eiffel.elicitation.template.variant.not-found")
-	// ErrInvalidFilepath will be displayed to the user if the filepath is invalid.
-	ErrInvalidFilepath = errors.New("eiffel.elicitation.output.file.path-invalid")
-	// ErrCouldNotCreateFile will be displayed to the user if the file could not be created.
-	ErrCouldNotCreateFile = errors.New("eiffel.elicitation.output.file.create-failed")
 )
 
 // TemplateDisplayType specifies how a rule should be displayed in the UI.
@@ -65,8 +61,6 @@ type TemplateFormData struct {
 	// SegmentMap is a map of rule names to their corresponding segment value.
 	// This is used to fill the segments with their values after parsing.
 	SegmentMap map[string]string
-	// OutputFormData is the form data for the output directory and file.
-	OutputFormData web.BaseTemplateData
 }
 
 // SearchTemplateData contains templates to render as search results and a flag indicating if the query was too short.
@@ -75,11 +69,8 @@ type SearchTemplateData struct {
 	QueryTooShort bool
 }
 
-// OutputFormData is the form data for the output directory and file search form.
-// It is used to fill the form with the previously selected values.
-type OutputFormData struct {
-	OutputDir  string
-	OutputFile string
+type HTMXTriggerParsingSuccessEvent struct {
+	ParsingSuccessEvent *parser.ParsingResult `json:"parsingSuccessEvent"`
 }
 
 // RegisterController registers the controllers as well as the navigation and event listeners.
@@ -102,9 +93,6 @@ func RegisterController(appCtx *hctx.AppCtx, webCtx *web.Ctx) {
 	router.Get("/eiffel/elicitation/{templateID}", elicitationTemplate(appCtx, webCtx, true).ServeHTTP)
 	router.Get("/eiffel/elicitation/{templateID}/{variant}", elicitationTemplate(appCtx, webCtx, false).ServeHTTP)
 	router.Post("/eiffel/elicitation/{templateID}/{variant}", parseRequirement(appCtx, webCtx, cfg).ServeHTTP)
-	router.Post("/eiffel/elicitation/output/search", outputSearchForm(appCtx, webCtx, cfg).ServeHTTP)
-	router.Post("/eiffel/elicitation/output/dir/search", outputSearchDir(appCtx, webCtx, cfg).ServeHTTP)
-	router.Post("/eiffel/elicitation/output/file/search", outputFileSearch(appCtx, webCtx, cfg).ServeHTTP)
 }
 
 func subscribeEvents(appCtx *hctx.AppCtx) {
@@ -185,7 +173,7 @@ func renderElicitationPage(io web.IO, data TemplateFormData, success []string, e
 		"eiffel/elicitation-page.go.html",
 		"eiffel/_elicitation-template.go.html",
 		"eiffel/_form-elicitation.go.html",
-		"eiffel/_form-output-file.go.html",
+		"eiffel/_list-requirements.go.html",
 	)
 }
 
@@ -274,8 +262,6 @@ func parseRequirement(appCtx *hctx.AppCtx, webCtx *web.Ctx, cfg Cfg) http.Handle
 
 		templateID := web.URLParam(request, "templateID")
 		variant := web.URLParam(request, "variant")
-		outputDir := BuildDirPath(cfg.Output.BaseDir, request.FormValue("elicitationOutputDir"))
-		outputFileRaw := request.FormValue("elicitationOutputFile")
 
 		formData, err := TemplateFormFromRequest(
 			ctx,
@@ -290,14 +276,9 @@ func parseRequirement(appCtx *hctx.AppCtx, webCtx *web.Ctx, cfg Cfg) http.Handle
 			return io.InlineError(err)
 		}
 
-		if outputFileRaw == "" {
-			outputFileRaw = formData.Template.Name
-		}
-		outputFile := BuildFilename(outputFileRaw)
-
 		segmentMap, err := SegmentMapFromRequest(request, len(formData.Variant.Rules))
 		if err != nil {
-			return io.InlineError(web.ErrInternal)
+			return io.InlineError(web.ErrInternal, err)
 		}
 		formData.SegmentMap = segmentMap
 
@@ -312,24 +293,15 @@ func parseRequirement(appCtx *hctx.AppCtx, webCtx *web.Ctx, cfg Cfg) http.Handle
 		}
 
 		if parsingResult.Ok() {
-			usr := user.MustCtxUser(ctx)
-			csv, created, err := CreateIfNotExists(outputDir, outputFile, 0750)
-			if err != nil {
-				return io.InlineError(ErrCouldNotCreateFile, err)
-			}
-
-			writer := CSVWriter{file: csv}
-			if created {
-				err := writer.WriteHeaderRow()
-				if err != nil {
-					return io.InlineError(web.ErrInternal, err)
-				}
-			}
-
-			err = writer.WriteRow(parsingResult, usr)
+			triggerEvent := &HTMXTriggerParsingSuccessEvent{ParsingSuccessEvent: &parsingResult}
+			triggerEventJSON, err := json.Marshal(triggerEvent)
 			if err != nil {
 				return io.InlineError(web.ErrInternal, err)
 			}
+
+			// Using HX-Trigger header here is not possible because we could potentially use unicode characters in our response.
+			// To escape them we have to use base64 encoding and a custom header.
+			io.Response().Header().Set("ParsingSuccessEvent", base64.URLEncoding.EncodeToString(triggerEventJSON))
 		}
 
 		formData.CopyAfterParse = CopyAfterParseSetting(request, sessionStore, false)
@@ -337,78 +309,3 @@ func parseRequirement(appCtx *hctx.AppCtx, webCtx *web.Ctx, cfg Cfg) http.Handle
 		return io.Render(web.NewFormData(formData, s, err), "eiffel.elicitation.form", "eiffel/_form-elicitation.go.html")
 	})
 }
-
-func outputSearchForm(appCtx *hctx.AppCtx, webCtx *web.Ctx, cfg Cfg) http.Handler {
-	return web.NewController(appCtx, webCtx, func(io web.IO) error {
-		request := io.Request()
-		err := request.ParseForm()
-		if err != nil {
-			return io.InlineError(web.ErrInternal, err)
-		}
-
-		rawDir := request.FormValue("output-dir")
-		dir := BuildDirPath(cfg.Output.BaseDir, rawDir)
-		rawFile := request.FormValue("output-file")
-		file := BuildFilename(rawFile)
-
-		path := filepath.Join(dir, file)
-		if path == "" {
-			return io.InlineError(ErrInvalidFilepath)
-		}
-
-		csv, created, err := CreateIfNotExists(dir, file, 0750)
-		if err != nil {
-			return io.InlineError(ErrCouldNotCreateFile, err)
-		}
-
-		if created {
-			writer := CSVWriter{file: csv}
-			err = writer.WriteHeaderRow()
-			if err != nil {
-				return io.InlineError(web.ErrInternal, err)
-			}
-		}
-
-		return io.Render(web.NewFormData(OutputFormData{
-			OutputDir:  rawDir,
-			OutputFile: rawFile,
-		}, []string{"eiffel.elicitation.output.file.success"}), "eiffel.elicitation.output-file.form", "eiffel/_form-output-file.go.html")
-	})
-}
-
-func outputSearchDir(appCtx *hctx.AppCtx, webCtx *web.Ctx, cfg Cfg) http.Handler {
-	return web.NewController(appCtx, webCtx, func(io web.IO) error {
-		request := io.Request()
-		err := request.ParseForm()
-		if err != nil {
-			return io.InlineError(web.ErrInternal, err)
-		}
-
-		query := request.FormValue("output-dir")
-		dirs, err := DirSearch(cfg.Output.BaseDir, query)
-		if err != nil {
-			return io.InlineError(web.ErrInternal, err)
-		}
-
-		return io.Render(dirs, "eiffel.elicitation.output.dir.search-result", "eiffel/_output-dir-search-result.go.html")
-	})
-}
-
-func outputFileSearch(appCtx *hctx.AppCtx, webCtx *web.Ctx, cfg Cfg) http.Handler {
-	return web.NewController(appCtx, webCtx, func(io web.IO) error {
-		request := io.Request()
-		err := request.ParseForm()
-		if err != nil {
-			return io.InlineError(web.ErrInternal, err)
-		}
-
-		query := request.FormValue("output-file")
-		dir := request.FormValue("output-dir")
-		files, err := FileSearch(BuildDirPath(cfg.Output.BaseDir, dir), query)
-		if err != nil {
-			return io.InlineError(web.ErrInternal, err)
-		}
-
-		return io.Render(files, "eiffel.elicitation.output.file.search-result", "eiffel/_output-file-search-result.go.html")
-	})
-}
diff --git a/src/app/template/parser/parser.go b/src/app/template/parser/parser.go
index afae82cb4d1aff8c88be07ea1192c6165b6451d6..d8c3f66be64adccd7445442cf81a7d05d78839da 100644
--- a/src/app/template/parser/parser.go
+++ b/src/app/template/parser/parser.go
@@ -28,12 +28,12 @@ type ParsingSegment struct {
 
 // ParsingResult is the result of parsing a requirement using a template.
 type ParsingResult struct {
-	TemplateID      string
+	TemplateID      string `json:"templateID,omitempty"`
 	TemplateType    string
-	TemplateVersion string
-	TemplateName    string
-	VariantName     string
-	Requirement     string
+	TemplateVersion string `json:"templateVersion,omitempty"`
+	TemplateName    string `json:"templateName,omitempty"`
+	VariantName     string `json:"variantName,omitempty"`
+	Requirement     string `json:"requirement,omitempty"`
 	Errors          []ParsingLog
 	Warnings        []ParsingLog
 	Notices         []ParsingLog
diff --git a/templates/eiffel/_list-requirements.go.html b/templates/eiffel/_list-requirements.go.html
new file mode 100644
index 0000000000000000000000000000000000000000..c96526cf92221cb0cd776ec7f3771a3696200ee9
--- /dev/null
+++ b/templates/eiffel/_list-requirements.go.html
@@ -0,0 +1,12 @@
+{{ define "eiffel.requirements.list" }}
+    <h3>{{ t "eiffel.output.recent.title" }}</h3>
+    <p>{{ t "eiffel.output.recent.description" }}</p>
+    <div id="eiffelRequirementsListWrapper">
+        <ul class="list-unstyled">
+            <li class="eiffel-requirements-list-item">
+                <b>{{ t "eiffel.output.recent.empty" }}</b>
+            </li>
+        </ul>
+    </div>
+    <button class="btn btn-outline-secondary w-100 mt-2" id="eiffelRequirementsEmpty">{{ t "eiffel.output.recent.empty-button" }}</button>
+{{ end }}
\ No newline at end of file
diff --git a/templates/eiffel/elicitation-page.go.html b/templates/eiffel/elicitation-page.go.html
index 323e6f2109fd6d211c98858cf54c44ce62022331..0865ab616c4fa24103cbc1fc48f959b31482dc7a 100644
--- a/templates/eiffel/elicitation-page.go.html
+++ b/templates/eiffel/elicitation-page.go.html
@@ -15,7 +15,7 @@
         </div>
 
         <div class="col-4 eiffel-requirements">
-            {{ template "eiffel.elicitation.output-file.form" .Data.Form.OutputFormData }}
+            {{ template "eiffel.requirements.list" . }}
         </div>
     </div>
 {{ end }}
\ No newline at end of file
diff --git a/translations/de.json b/translations/de.json
index cdf3107b314ee2dc95f886b7c3ed207737a9da4a..26c59430267dafe8d290d814be4944a050dce3e2 100644
--- a/translations/de.json
+++ b/translations/de.json
@@ -128,18 +128,6 @@
         "value-single-select": "Ein Wert aus",
         "copy-and-clear": "Kopieren und leeren"
       },
-      "output": {
-        "title": "Ausgabedatei festlegen (Alt + O)",
-        "file": "Ausgabedatei",
-        "file.success": "Ausgabedatei für Erfassung festgelegt.",
-        "file.save": "Ausgabedatei für Erfassung speichern",
-        "file.help": "Die Ausgabedatei wird automatisch erstellt, wenn sie nicht existiert. Anforderungen werden angehängt. Die \".csv\"-Dateierweiterung wird automatisch hinzugefügt.",
-        "directory": "Ausgabeverzeichnis",
-        "directory.help": "Unterverzeichnisse sollten durch \"\/\" getrennt werden. Nicht existierende Verzeichnisse werden automatisch erstellt.",
-        "file.info": "Legen Sie eine Ausgabedatei fest, in die Anforderungen nach der Prüfung automatisch geschrieben werden. Somit gehen keine Anforderungen verloren.",
-        "file.path-invalid": "Der Pfad ist ungültig. Bitte überprüfen Sie den Pfad und versuchen Sie es erneut.",
-        "file.create-failed": "Die Datei konnte nicht erstellt werden. Bitte überprüfen Sie den Pfad und die Dateiberechtigungen und versuchen Sie es erneut."
-      },
       "template": {
         "search": {
           "title": "Schablone suchen",
@@ -166,6 +154,14 @@
         "settings": "Einstellungen",
         "copy-after-parse": "Anforderung nach erfolgreicher Prüfung automatisch kopieren und das Formular leeren (manuell: Alt + K)"
       }
+    },
+    "output": {
+      "recent": {
+        "title": "Zuletzt erfasste Anforderungen",
+        "description": "Ihre 420 letzten erfassten Anforderungen werden auf Ihrem Gerät gespeichert und hier angezeigt. Sie können diese Anforderungen durch Klicken kopieren.",
+        "empty": "Es wurden noch keine Anforderungen erfasst.",
+        "empty-button": "Letzte Anforderungen leeren"
+      }
     }
   },
   "harmony": {