diff --git a/.gitignore b/.gitignore
index 49ec817fb5545fecadd2ddbdd2c4add7c0c79d3e..88b5cec8dafb2d4c5e45e0d5dbcfe52f15092b13 100644
--- a/.gitignore
+++ b/.gitignore
@@ -23,6 +23,9 @@ go.work
 var/
 tmp/
 
+# HARMONY specific
 config/**/*.local.toml
+files/**/*
+!files/.gitkeep
 
 .idea
diff --git a/config/eiffel.toml b/config/eiffel.toml
new file mode 100644
index 0000000000000000000000000000000000000000..5f606d0e32a1341c50e2e8ac2819adc0f6c12809
--- /dev/null
+++ b/config/eiffel.toml
@@ -0,0 +1,2 @@
+[output]
+base_dir = "files/eiffel"
\ No newline at end of file
diff --git a/files/.gitkeep b/files/.gitkeep
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/app/eiffel/eiffel.go b/src/app/eiffel/eiffel.go
new file mode 100644
index 0000000000000000000000000000000000000000..b6e3e22010c918c535434baebc8c90870e79f4f9
--- /dev/null
+++ b/src/app/eiffel/eiffel.go
@@ -0,0 +1,7 @@
+// Package eiffel contains necessary functionality for the Elicitation Interface for eFFective Language (EIFFEL).
+package eiffel
+
+// Cfg is EIFFEL's configuration struct. This can be used to unmarshal a TOML configuration file into.
+type Cfg struct {
+	Output OutputCfg `toml:"output"`
+}
diff --git a/src/app/eiffel/output.go b/src/app/eiffel/output.go
new file mode 100644
index 0000000000000000000000000000000000000000..a373a8ffa0819c28d939e5e7737daeae1098cce6
--- /dev/null
+++ b/src/app/eiffel/output.go
@@ -0,0 +1,202 @@
+package eiffel
+
+import (
+	"encoding/csv"
+	"fmt"
+	"github.com/org-harmony/harmony/src/app/template/parser"
+	"github.com/org-harmony/harmony/src/app/user"
+	"os"
+	"path/filepath"
+	"regexp"
+	"strings"
+	"time"
+)
+
+// OutputWriter defines the common interface for all EIFFEL's output writers.
+// Most prominently, this is the CSVWriter.
+type OutputWriter interface {
+	// WriteHeaderRow writes the header row to the output file. The header row contains the names of the columns.
+	// This is most important for OutputWriter implementations that write to files.
+	// The header row should be written only once per file.
+	WriteHeaderRow() error
+	// WriteRow writes a row to the output file. The row contains the values of the columns.
+	// The values are taken from the parser.ParsingResult and the user.User.
+	WriteRow(pr parser.ParsingResult, usr *user.User) error
+}
+
+// OutputCfg contains the configuration for the output of EIFFEL.
+type OutputCfg struct {
+	// BaseDir is the base directory for the output of EIFFEL.
+	// Each requirement will be written to an output file lying in a (sub-)directory of the base directory.
+	// EIFFEL will create the (sub-)directories if they do not exist.
+	// BaseDir should be "files/eiffel".
+	BaseDir string `toml:"base_dir" env:"EIFFEL_OUTPUT_BASE_DIR" hvalidate:"required"`
+}
+
+// CSVWriter is an OutputWriter that writes to a CSV-file.
+// The CSV-file contains the following columns: Requirement, Date, Time, Template, Variant, Template Version, Author.
+// The CSVWriter requires a file handle to the output file. Ensure sufficient permissions for the file.
+type CSVWriter struct {
+	file *os.File
+}
+
+// DirSearch searches the baseDir for (sub-)directories containing the query string.
+// The search is case-insensitive. Returns a slice of matching (sub-)directories.
+// If no (sub-)directories match the query, an empty slice is returned.
+// The returned slice contains the relative path to the (sub-)directories from the baseDir.
+func DirSearch(baseDir string, query string) ([]string, error) {
+	var dirs []string
+	queryLower := strings.ToLower(query)
+
+	err := filepath.WalkDir(baseDir, func(path string, d os.DirEntry, err error) error {
+		if err != nil {
+			return err
+		}
+
+		if !d.IsDir() {
+			return nil
+		}
+
+		if path == baseDir {
+			return nil
+		}
+
+		visiblePath := strings.TrimPrefix(path, baseDir+"/")
+		if strings.Contains(strings.ToLower(d.Name()), queryLower) || strings.Contains(strings.ToLower(visiblePath), queryLower) {
+			dirs = append(dirs, visiblePath)
+		}
+
+		return nil
+	})
+
+	if err != nil {
+		return nil, err
+	}
+
+	return dirs, nil
+}
+
+// FileSearch searches the base directory + a specified sub-path for .csv-files containing the query string in their name.
+// Only files with the .csv-extension are considered. The search is case-insensitive. Returns a slice of matching files.
+func FileSearch(baseDir string, subPath string, query string) ([]string, error) {
+	var files []string
+	queryLower := strings.ToLower(query)
+
+	if subPath != "" {
+		subPath = filepath.Clean(subPath)
+	}
+
+	err := filepath.WalkDir(filepath.Join(baseDir, subPath), func(path string, d os.DirEntry, err error) error {
+		if err != nil {
+			return err
+		}
+
+		if d.IsDir() {
+			return nil
+		}
+
+		filenameExt := d.Name()
+		ext := filepath.Ext(filenameExt)
+		if strings.ToLower(ext) != ".csv" {
+			return nil
+		}
+
+		name := strings.TrimSuffix(filenameExt, ext)
+		if strings.Contains(strings.ToLower(name), queryLower) {
+			files = append(files, name)
+		}
+
+		return nil
+	})
+
+	if err != nil {
+		return nil, err
+	}
+
+	return files, nil
+}
+
+// BuildDirPath takes in a base dir + sub-path and returns the sanitized, full dir-path.
+// The sub-path can be empty. The sub-path is sanitized before being used in the dir-path.
+// The baseDir is not sanitized! It is assumed to be safe.
+// Every character that is not a letter, number, underscore or hyphen is replaced by an underscore.
+func BuildDirPath(baseDir, subPath string) string {
+	return filepath.Join(baseDir, filepath.Clean(SanitizeFilepath(subPath)))
+}
+
+// BuildFilename takes in a filename (w/o extension) and returns the sanitized filename with the .csv-extension.
+func BuildFilename(filename string) string {
+	return fmt.Sprintf("%s.csv", SanitizeFilepath(filename))
+}
+
+// SanitizeFilepath takes in a filepath and returns the sanitized filepath.
+// Every character that is not a letter, number, underscore or hyphen is replaced by an underscore.
+func SanitizeFilepath(path string) string {
+	reg := regexp.MustCompile(`[^/a-zA-Z0-9_-]+`)
+
+	return reg.ReplaceAllString(path, "_")
+}
+
+// CreateIfNotExists creates the specified file in a directory and all the necessary directories if they do not exist.
+// The file is created with the specified permissions. If the file already exists, nothing happens.
+// CreateIfNotExists returns the file, a boolean indicating whether the file was created and an error.
+func CreateIfNotExists(dirPath, filename string, perm os.FileMode) (*os.File, bool, error) {
+	if _, err := os.Stat(dirPath); os.IsNotExist(err) {
+		if err := os.MkdirAll(dirPath, perm); err != nil {
+			return nil, false, err
+		}
+	}
+
+	filePath := filepath.Join(dirPath, filename)
+	if _, err := os.Stat(filePath); os.IsNotExist(err) {
+		if file, err := os.Create(filePath); err == nil {
+			return file, true, nil
+		}
+	}
+
+	file, err := os.OpenFile(filePath, os.O_RDWR|os.O_APPEND, perm)
+
+	return file, false, err
+}
+
+// WriteHeaderRow writes the header row to the output file. The header row contains the names of the columns.
+// The columns are: Requirement, Date, Time, Template, Variant, Template Version, Author.
+func (c *CSVWriter) WriteHeaderRow() error {
+	header := []string{"Requirement", "Date", "Time", "Template", "Variant", "Template Version", "Author"}
+	writer := csv.NewWriter(c.file)
+
+	err := writer.Write(header)
+	if err != nil {
+		return err
+	}
+
+	writer.Flush()
+
+	return nil
+}
+
+// WriteRow writes a row to the output file. The row contains the values of the columns.
+// The values are the requirement, the current date and time, the template name, the variant name, the template version and the author.
+func (c *CSVWriter) WriteRow(pr parser.ParsingResult, usr *user.User) error {
+	now := time.Now()
+	row := []string{
+		pr.Requirement,
+		now.Format("2006-01-02"),
+		now.Format("15:04:05"),
+		pr.TemplateName,
+		pr.VariantName,
+		pr.TemplateVersion,
+		fmt.Sprintf("%s %s", usr.Firstname, usr.Lastname),
+	}
+
+	writer := csv.NewWriter(c.file)
+
+	err := writer.Write(row)
+	if err != nil {
+		return err
+	}
+
+	writer.Flush()
+
+	return nil
+}
diff --git a/src/app/eiffel/parser.go b/src/app/eiffel/parser.go
index 6b62a642d307212fa3834ec72187309cb4a516c7..9b4d22c1385d0b68050a08ed4bc4d721fef8f892 100644
--- a/src/app/eiffel/parser.go
+++ b/src/app/eiffel/parser.go
@@ -181,8 +181,7 @@ func RuleParsers() *RuleParserProvider {
 // Parse implements the template.ParsableTemplate interface for the BasicTemplate. It is used to parse requirements in the form of segments.
 // Each segment is a part of the requirement that is to be parsed. For the EIFFEL basic template (EBT), each segment is an input from auto-generated
 // input field based on the rules defined in the template. Therefore, each segment will be validated by the corresponding rule.
-//
-// This function might panic if the template is not valid. Therefore, it is recommended to validate the template before parsing requirements.
+// It is recommended to validate the template before parsing requirements.
 //
 // The parsing process is as follows:
 //  1. Prepare segments by trimming whitespaces from the input string and indexing them.
@@ -200,10 +199,12 @@ func RuleParsers() *RuleParserProvider {
 // Also, it is possible that a parsed rule contains warnings those are not downgraded and the parsing result will not be flawless.
 func (bt *BasicTemplate) Parse(ctx context.Context, ruleParsers *RuleParserProvider, variantName string, segments ...parser.ParsingSegment) (parser.ParsingResult, error) {
 	result := parser.ParsingResult{
-		TemplateID:   bt.ID,
-		TemplateType: BasicTemplateType,
-		TemplateName: bt.Name,
-		VariantName:  variantName,
+		TemplateID:      bt.ID,
+		TemplateType:    BasicTemplateType,
+		TemplateVersion: bt.Version,
+		TemplateName:    bt.Name,
+		VariantName:     variantName,
+		Requirement:     "",
 	}
 
 	indexedSegments := prepareSegments(segments)
@@ -211,6 +212,7 @@ func (bt *BasicTemplate) Parse(ctx context.Context, ruleParsers *RuleParserProvi
 	if !ok {
 		return result, ErrInvalidVariant
 	}
+	result.VariantName = variant.Name
 
 	for _, ruleName := range variant.Rules {
 		rule, ok := bt.Rules[ruleName]
@@ -219,9 +221,9 @@ func (bt *BasicTemplate) Parse(ctx context.Context, ruleParsers *RuleParserProvi
 		}
 
 		segment, ok := indexedSegments[ruleName]
-		if !ok && !rule.Optional {
+		if (!ok || segment.Value == "") && !rule.Optional {
 			result.Errors = append(result.Errors, parser.ParsingLog{
-				Segment: nil,
+				Segment: &parser.ParsingSegment{Name: ruleName},
 				Level:   parser.ParsingLogLevelError,
 				Message: "eiffel.parser.error.missing-segment",
 				TranslationArgs: []string{
@@ -234,7 +236,7 @@ func (bt *BasicTemplate) Parse(ctx context.Context, ruleParsers *RuleParserProvi
 			continue
 		}
 
-		if !ok {
+		if !ok || segment.Value == "" {
 			continue // rule is optional and segment is missing -> ignore
 		}
 
@@ -248,6 +250,19 @@ func (bt *BasicTemplate) Parse(ctx context.Context, ruleParsers *RuleParserProvi
 			return result, err
 		}
 
+		be := " "
+		af := ""
+		before, bOk := rule.Extra["before"]
+		after, aOk := rule.Extra["after"]
+		if bStr, ok := before.(string); ok && bOk {
+			be = bStr
+		}
+		if aStr, ok := after.(string); ok && aOk {
+			af = aStr
+		}
+
+		result.Requirement += be + strings.TrimSpace(segment.Value) + af
+
 		for _, log := range parsingLogs {
 			switch log.Level {
 			case parser.ParsingLogLevelError:
@@ -266,6 +281,8 @@ func (bt *BasicTemplate) Parse(ctx context.Context, ruleParsers *RuleParserProvi
 		}
 	}
 
+	result.Requirement = strings.TrimSpace(result.Requirement)
+
 	return result, nil
 }
 
diff --git a/src/app/eiffel/service.go b/src/app/eiffel/service.go
index b70059d6a2ae78942c61b6467007910d6b0f88e5..164e263d5bd74814ac53f3233e979754d3cbb466 100644
--- a/src/app/eiffel/service.go
+++ b/src/app/eiffel/service.go
@@ -5,7 +5,10 @@ import (
 	"encoding/json"
 	"github.com/google/uuid"
 	"github.com/org-harmony/harmony/src/app/template"
+	"github.com/org-harmony/harmony/src/app/template/parser"
 	"github.com/org-harmony/harmony/src/core/validation"
+	"net/http"
+	"strings"
 )
 
 // TemplateDisplayTypes returns a map of rule names to display types. The rule names are the keys of the BasicTemplate.Rules map.
@@ -100,3 +103,52 @@ func TemplateFormFromRequest(
 		TemplateID:   templateUUID,
 	}, nil
 }
+
+// SegmentMapFromRequest parses the segments from the request and returns a map of segment names to values.
+// The length parameter is used to initialize the map with a given length. If the length is 0, the map will be
+// initialized with a length of 0, no error will occur. The length is only used for pre-allocation.
+//
+// Segments are expected to be in the form of "segment-<name>".
+//
+// Use SegmentMapToSegments to convert the map into a slice of ParsingSegments.
+func SegmentMapFromRequest(request *http.Request, length int) (map[string]string, error) {
+	err := request.ParseForm()
+	if err != nil {
+		return nil, err
+	}
+
+	var segments map[string]string
+	if length > 0 {
+		segments = make(map[string]string, length)
+	} else {
+		segments = make(map[string]string)
+	}
+
+	for name, values := range request.Form {
+		if !strings.HasPrefix(name, "segment-") {
+			continue
+		}
+
+		if len(values) < 1 {
+			continue
+		}
+
+		segments[strings.TrimPrefix(name, "segment-")] = values[0]
+	}
+
+	return segments, nil
+}
+
+// SegmentMapToSegments converts a map of segment names to values into a slice of ParsingSegments.
+// The order of the segments in the slice is not guaranteed. The map can be generated using SegmentMapFromRequest.
+func SegmentMapToSegments(segments map[string]string) []parser.ParsingSegment {
+	parsingSegments := make([]parser.ParsingSegment, 0, len(segments))
+	for name, value := range segments {
+		parsingSegments = append(parsingSegments, parser.ParsingSegment{
+			Name:  name,
+			Value: value,
+		})
+	}
+
+	return parsingSegments
+}
diff --git a/src/app/eiffel/web.go b/src/app/eiffel/web.go
index e1ad9ee651633a1b925d6437b0676e0ff1aa0789..381ba3a71e5fb6b81fe4df0aebb6abad2a555bce 100644
--- a/src/app/eiffel/web.go
+++ b/src/app/eiffel/web.go
@@ -6,7 +6,9 @@ import (
 	"fmt"
 	"github.com/google/uuid"
 	"github.com/org-harmony/harmony/src/app/template"
+	"github.com/org-harmony/harmony/src/app/template/parser"
 	"github.com/org-harmony/harmony/src/app/user"
+	"github.com/org-harmony/harmony/src/core/config"
 	"github.com/org-harmony/harmony/src/core/event"
 	"github.com/org-harmony/harmony/src/core/hctx"
 	"github.com/org-harmony/harmony/src/core/persistence"
@@ -14,6 +16,7 @@ import (
 	"github.com/org-harmony/harmony/src/core/validation"
 	"github.com/org-harmony/harmony/src/core/web"
 	"net/http"
+	"path/filepath"
 	"strings"
 )
 
@@ -33,6 +36,10 @@ 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.
@@ -54,6 +61,13 @@ type TemplateFormData struct {
 	TemplateID uuid.UUID
 	// CopyAfterParse is a flag indicating if the user wants to copy the parsed requirement to the clipboard.
 	CopyAfterParse bool
+	// ParsingResult is the result of the parsing process. This can be empty if no parsing was done yet.
+	ParsingResult *parser.ParsingResult
+	// 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.
@@ -62,7 +76,17 @@ 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
+}
+
 func RegisterController(appCtx *hctx.AppCtx, webCtx *web.Ctx) {
+	cfg := Cfg{}
+	util.Ok(config.C(&cfg, config.From("eiffel"), config.Validate(appCtx.Validator)))
+
 	// TODO move this to module init when module manager is implemented (see subscribeEvents)
 	subscribeEvents(appCtx)
 
@@ -77,8 +101,10 @@ func RegisterController(appCtx *hctx.AppCtx, webCtx *web.Ctx) {
 	router.Post("/eiffel/elicitation/templates/search", searchTemplate(appCtx, webCtx).ServeHTTP)
 	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).ServeHTTP)
-	router.Get("/eiffel/elicitation/output/file/form", outputFileForm(appCtx, webCtx).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) {
@@ -232,14 +258,144 @@ func elicitationTemplate(appCtx *hctx.AppCtx, webCtx *web.Ctx, defaultFirstVaria
 	})
 }
 
-func parseRequirement(appCtx *hctx.AppCtx, webCtx *web.Ctx) http.Handler {
+func parseRequirement(appCtx *hctx.AppCtx, webCtx *web.Ctx, cfg Cfg) http.Handler {
+	templateRepository := util.UnwrapType[template.Repository](appCtx.Repository(template.RepositoryName))
+
+	return web.NewController(appCtx, webCtx, func(io web.IO) error {
+		request := io.Request()
+		ctx := request.Context()
+		parsers := RuleParsers()
+
+		templateID := web.URLParam(request, "templateID")
+		variant := web.URLParam(request, "variant")
+		outputDir := BuildDirPath(cfg.Output.BaseDir, request.FormValue("elicitationOutputDir"))
+		outputFile := BuildFilename(request.FormValue("elicitationOutputFile"))
+
+		formData, err := TemplateFormFromRequest(
+			ctx,
+			templateID,
+			variant,
+			templateRepository,
+			parsers,
+			appCtx.Validator,
+			false,
+		)
+		if err != nil {
+			return io.InlineError(err)
+		}
+
+		segmentMap, err := SegmentMapFromRequest(request, len(formData.Variant.Rules))
+		if err != nil {
+			return io.InlineError(web.ErrInternal)
+		}
+		formData.SegmentMap = segmentMap
+
+		parsingResult, err := formData.Template.Parse(ctx, parsers, formData.VariantKey, SegmentMapToSegments(segmentMap)...)
+		formData.ParsingResult = &parsingResult
+
+		var s []string
+		if parsingResult.Flawless() {
+			s = []string{"eiffel.elicitation.parse.flawless-success"}
+		} else if parsingResult.Ok() {
+			s = []string{"eiffel.elicitation.parse.success"}
+		}
+
+		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)
+			if err != nil {
+				return io.InlineError(web.ErrInternal, err)
+			}
+		}
+
+		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 {
-		return io.Render(nil, "eiffel.elicitation.form", "eiffel/_form-elicitation.go.html")
+		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 outputFileForm(appCtx *hctx.AppCtx, webCtx *web.Ctx) http.Handler {
+func outputFileSearch(appCtx *hctx.AppCtx, webCtx *web.Ctx, cfg Cfg) http.Handler {
 	return web.NewController(appCtx, webCtx, func(io web.IO) error {
-		return io.Render(nil, "eiffel.elicitation.output-file.form", "eiffel/_form-output-file.go.html")
+		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(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 3ebecafb907fa022cd6198fe9505307198c7b96e..afae82cb4d1aff8c88be07ea1192c6165b6451d6 100644
--- a/src/app/template/parser/parser.go
+++ b/src/app/template/parser/parser.go
@@ -28,13 +28,15 @@ type ParsingSegment struct {
 
 // ParsingResult is the result of parsing a requirement using a template.
 type ParsingResult struct {
-	TemplateID   string
-	TemplateType string
-	TemplateName string
-	VariantName  string
-	Errors       []ParsingLog
-	Warnings     []ParsingLog
-	Notices      []ParsingLog
+	TemplateID      string
+	TemplateType    string
+	TemplateVersion string
+	TemplateName    string
+	VariantName     string
+	Requirement     string
+	Errors          []ParsingLog
+	Warnings        []ParsingLog
+	Notices         []ParsingLog
 }
 
 // ParsingLog is a log entry of a parsing result. It contains the segment that was parsed, the level of the log and a message.
@@ -77,3 +79,19 @@ func (r ParsingResult) Ok() bool {
 func (r ParsingResult) Flawless() bool {
 	return len(r.Errors) == 0 && len(r.Warnings) == 0
 }
+
+// ViolationsForRule returns all violations (errors) for a given rule.
+// This can be used to check if a rule was violated and what to display to the user.
+// Warnings and Notices are not considered violations and will be displayed to the user as general information, not per rule.
+func (r ParsingResult) ViolationsForRule(rule string) []ParsingLog {
+	var violations []ParsingLog
+	for _, log := range r.Errors {
+		if log.Segment.Name != rule {
+			continue
+		}
+
+		violations = append(violations, log)
+	}
+
+	return violations
+}
diff --git a/src/cmd/web/main.go b/src/cmd/web/main.go
index 862d69c70d10e8907667ca50280b7b1d7d269f32..f0bbfd8010ca4d74c851b928d44cbcd3d64532e7 100644
--- a/src/cmd/web/main.go
+++ b/src/cmd/web/main.go
@@ -27,6 +27,7 @@ import (
 // TODO add utilities for easier testing of the web layer
 // TODO improve UI/UX/Design (styling, css, scss)
 // TODO add more loading indications, especially for loading body changes
+// TODO improve user logged out handling during requests/responses and general site interaction/navigation
 
 func main() {
 	logger := trace.NewLogger()
diff --git a/src/core/trans/trans.go b/src/core/trans/trans.go
index 772822b455ddcf62e1ffb173a1524d3b7500b35c..615e5a58fbaf076dfbb5e379fb8a883080fcae88 100644
--- a/src/core/trans/trans.go
+++ b/src/core/trans/trans.go
@@ -69,8 +69,8 @@ type HTranslatorProvider struct {
 // HTranslatorOption is a functional option for the HTranslator.
 type HTranslatorOption func(*HTranslator)
 
-// Error is an interface for errors that can be translated.
-type Error interface {
+// Translatable is an interface for errors that can be translated.
+type Translatable interface {
 	Translate(Translator) string
 }
 
@@ -138,6 +138,10 @@ func NewTranslator(opts ...HTranslatorOption) Translator {
 
 // T translates a string.
 func (t *HTranslator) T(s string) string {
+	if t == nil {
+		return s
+	}
+
 	transS, ok := t.translations[s]
 	if !ok {
 		return s
@@ -153,6 +157,10 @@ func (t *HTranslator) T(s string) string {
 //
 // This parsing of args is done by the ArgsAsMap function.
 func (t *HTranslator) Tf(s string, args ...string) string {
+	if t == nil {
+		return s
+	}
+
 	var err error
 	s = t.T(s)
 	hash := md5.New()
@@ -187,6 +195,10 @@ func (t *HTranslator) Tf(s string, args ...string) string {
 
 // Locale returns the locale the translator translates to.
 func (t *HTranslator) Locale() *Locale {
+	if t == nil {
+		return nil
+	}
+
 	return t.locale
 }
 
diff --git a/src/core/web/ui.go b/src/core/web/ui.go
index 4097e85c25635dbcc59b9174755ab83993840b29..51f8de0933eb2b53f1aee0f8213dd82c4cd4a0a8 100644
--- a/src/core/web/ui.go
+++ b/src/core/web/ui.go
@@ -444,6 +444,7 @@ func (d *FormData[T]) ValidationErrorsForField(field string) []validation.Error
 }
 
 // AllValidationErrors returns all validation errors for all fields.
+// If you want to display all errors to the user use AllViolations instead.
 func (d *FormData[T]) AllValidationErrors() []validation.Error {
 	var errs []validation.Error
 	for _, fieldErrs := range d.Violations {
@@ -458,27 +459,20 @@ func (d *FormData[T]) AllValidationErrors() []validation.Error {
 	return errs
 }
 
-// AllTranslatableErrors returns all errors for all fields that can be read as trans.Error.
-// This is useful for translating errors before displaying them to the user.
-func (d *FormData[T]) AllTranslatableErrors() []trans.Error {
-	var errs []trans.Error
-	for _, fieldErrs := range d.Violations {
-		for _, err := range fieldErrs {
-			var t trans.Error
-			if errors.As(err, &t) {
-				errs = append(errs, t)
-			}
-		}
-	}
-
-	return errs
-}
-
 // AllViolations returns all errors for all fields. They can then be used to display all errors to the user.
+//
+// Important: AllViolations does *not* return any validation errors. Use AllValidationErrors for that.
+// ValidationErrors are filtered out because they are usually displayed to the user in a different way than other errors.
 func (d *FormData[T]) AllViolations() []error {
 	var errs []error
 	for _, fieldErrs := range d.Violations {
-		errs = append(errs, fieldErrs...)
+		for _, err := range fieldErrs {
+			if errors.Is(err, &validation.Error{}) {
+				continue
+			}
+
+			errs = append(errs, err)
+		}
 	}
 
 	return errs
@@ -564,13 +558,16 @@ func makeTemplateTranslatable(ctx context.Context, t *template.Template) error {
 		"tf": func(s string, args ...string) string {
 			return translator.Tf(s, args...)
 		},
-		"tErrs": func(errs []trans.Error) []string {
-			var s []string
-			for _, err := range errs {
-				s = append(s, err.Translate(translator))
+		"tryTranslate": func(t any) string {
+			if s, ok := t.(string); ok {
+				return translator.T(s)
 			}
 
-			return s
+			if t, ok := t.(trans.Translatable); ok {
+				return t.Translate(translator)
+			}
+
+			return fmt.Sprintf("%s", t)
 		},
 	})
 
@@ -580,6 +577,9 @@ func makeTemplateTranslatable(ctx context.Context, t *template.Template) error {
 // templateFuncs returns a template.FuncMap containing basic template functions.
 func templateFuncs(ui *UICfg) template.FuncMap {
 	return template.FuncMap{
+		"add": func(a, b int) int {
+			return a + b
+		},
 		"asset": func(filename string) string {
 			return filepath.Join(ui.AssetsUri, filename)
 		},
@@ -592,13 +592,12 @@ func templateFuncs(ui *UICfg) template.FuncMap {
 		"tf": func(s string, args ...string) string {
 			return s
 		},
-		"tErrs": func(errs []error) []string {
-			var s []string
-			for _, err := range errs {
-				s = append(s, err.Error())
+		"tryTranslate": func(t any) string {
+			if s, ok := t.(string); ok {
+				return s
 			}
 
-			return s
+			return fmt.Sprintf("%s", t)
 		},
 	}
 }
diff --git a/templates/base/layout.go.html b/templates/base/layout.go.html
index 673027d4f17985d2d612d2fc7ce5f071b939d722..9c3b1fad56de91cf9db7d6dce850cbf398c3b3fe 100644
--- a/templates/base/layout.go.html
+++ b/templates/base/layout.go.html
@@ -23,7 +23,7 @@
 
     {{ block "content-container" . }}
         <section class="section content-section mt-3">
-            <div class="content-container container col-8">
+            <div class="content-container container">
                 <div id="content">
                     {{ template "content" . }}
                 </div>
diff --git a/templates/eiffel/_elicitation-template.go.html b/templates/eiffel/_elicitation-template.go.html
index ef8928fd0217862f4d861ebfef236015c14a9084..0ba7f4acc62badd4a8e67df54dc9c861467bc294 100644
--- a/templates/eiffel/_elicitation-template.go.html
+++ b/templates/eiffel/_elicitation-template.go.html
@@ -9,13 +9,11 @@
             <div class="row">
                 <div class="col">
                     {{ range $key, $variant := .Data.Form.Template.Variants }}
-                        <input id="eiffelVariant-{{ $key }}"
-                               hx-get="/eiffel/elicitation/{{ $templateID }}/{{ $key }}"
-                               hx-target="#eiffelElicitationTemplate"
-                               type="radio" name="options-base"
-                               {{ if eq $variantKey $key }}checked{{ end }}
-                               autocomplete="off" class="btn-check"/>
-                        <label class="btn" for="eiffelVariant-{{ $key }}">{{ $variant.Name }}</label>
+                        <button hx-get="/eiffel/elicitation/{{ $templateID }}/{{ $key }}"
+                            hx-target="#eiffelElicitationTemplate"
+                            class="btn {{ if eq $variantKey $key }}btn-outline-primary{{ else }}btn-outline-secondary{{ end }}">
+                            {{ $variant.Name }}
+                        </button>
                     {{ end }}
                 </div>
             </div>
@@ -103,10 +101,10 @@
                 <div id="collapseSettings" class="accordion-collapse collapse" aria-labelledby="headingSettings" data-bs-parent="#eiffelTemplateInfoAccordion">
                     <div class="accordion-body">
                         <div class="form-check">
-                            <input form="eiffelElicitationForm" class="form-check-input" type="checkbox"
-                               name="copyAfterParse" id="copyAfterParse"
+                            <input form="eiffelElicitationForm" class="form-check-input" role="button"
+                               type="checkbox" name="copyAfterParse" id="copyAfterParse"
                                {{ if .Data.Form.CopyAfterParse }}checked{{ end }}/>
-                            <label class="form-check-label" for="copyAfterParse">
+                            <label class="form-check-label" for="copyAfterParse" role="button">
                                 {{ t "eiffel.elicitation.template.copy-after-parse" }}
                             </label>
                         </div>
@@ -115,19 +113,6 @@
             </div>
         </div>
 
-        {{/*
-        Hier muss von Anfang eine Variante als selektiert reingegeben werden. Bei initialem Laden einfach die 1. Variante nehmen.
-        Ebenfalls beim Suchen und Auswählen der Schablone. (Gleichzusetzen mit Neuladen der Seite.)
-        Beim Nachladen/Verändern der Variante wird hier die gewählte Variante übergeben und vorausgewählt.
-
-        Klicken der Variante wechselt damit die Variante und erzeugt einen HTMX Request, der die Variante wechselt.
-        Damit wird auch die URL für das Formular verändert, diese enthält Schablone und Variante.
-
-        Beim Abschicken und Validieren des Forms (Parsing) muss die Variante übergeben werden und im Gegenzug wird lediglich das Formular
-        zurückgegeben. Die aktuell ausgewählte Schablone und Variante sollten mit übergeben werden (über die URL).
-        Die ausgewählte Datei sollte über einen Input außerhalb des Formulars übergeben werden. (Hier kann das Input-Attribut "form" verwendet werden.)
-        */}}
-
         <div class="eiffel-elicitation-template-variant-form mt-3 w-100">
             {{ template "eiffel.elicitation.form" . }}
         </div>
diff --git a/templates/eiffel/_form-elicitation.go.html b/templates/eiffel/_form-elicitation.go.html
index 4a4160fbfd5d1e944147d6f45ca2e1e61d1e2033..cb5213d6f0a52e89f41656d29ce97b143dcb5931 100644
--- a/templates/eiffel/_form-elicitation.go.html
+++ b/templates/eiffel/_form-elicitation.go.html
@@ -1,15 +1,219 @@
 {{ define "eiffel.elicitation.form" }}
-    <h4>{{ t "eiffel.elicitation.template.form.title" }}</h4>
-    <form
-            hx-post="/eiffel/elicitation/{id}"
-            hx-target="#eiffelElicitationForm"
-            hx-disabled-elt=".eiffel-elicitation-form-fieldset"
-            hx-swap="outerHTML"
-            hx-trigger="submit"
-            id="eiffelElicitationForm"
-    >
+    {{ $rules := .Data.Form.Template.Rules }}
+    {{ $displayTypes := .Data.Form.DisplayTypes }}
+    {{ $parsingResult := .Data.Form.ParsingResult }}
+    {{ $segments := .Data.Form.SegmentMap }}
+
+    {{/*
+    TODO save copy to clipboard setting to user session
+    TODO JS for better UX => automatically active input etc.
+    TODO add copy to clipboard
+    TODO shortcuts hinzufügen
+    */}}
+
+    <h4>{{ t "eiffel.elicitation.form.title" }}</h4>
+    <form hx-post="/eiffel/elicitation/{{ .Data.Form.TemplateID }}/{{ .Data.Form.VariantKey }}"
+        hx-target=".eiffel-elicitation-template-variant-form"
+        hx-disabled-elt=".eiffel-elicitation-form-fieldset"
+        id="eiffelElicitationForm">
         <fieldset class="eiffel-elicitation-form-fieldset">
-            <input name="test" placeholder="test" />
+            <div class="row">
+                {{/* TODO beautify this code and improve readability - good templating is hard :/ */}}
+
+                {{ range $i, $ruleName := .Data.Form.Variant.Rules }}
+                    {{ $rule := index $rules . }}
+                    {{ $displayType := index $displayTypes $ruleName }}
+                    {{ $col := "col-6" }}
+
+                    {{ if eq $rule.Size "small" }}
+                        {{ $col = "col-3" }}
+                    {{ else if eq $rule.Size "medium" }}
+                        {{ $col = "col-6" }}
+                    {{ else if eq $rule.Size "large" }}
+                        {{ $col = "col-9" }}
+                    {{ else if eq $rule.Size "full" }}
+                        {{ $col = "col-12" }}
+                    {{ end }}
+
+                    {{ $violations := "" }}
+                    {{ if $parsingResult }}
+                        {{ $violations = $parsingResult.ViolationsForRule $ruleName }}
+                    {{ end }}
+
+                    {{ $displayName := $rule.Name }}
+                    {{ if not $rule.Optional }}
+                        {{ $displayName = printf "%s %s" $displayName "*" }}
+                    {{ end }}
+
+                    {{ $inputName := printf "segment-%s" $ruleName }}
+
+                    <div class="{{ $col }}">
+                        {{ if or (eq $displayType "input-text")
+                        (eq $displayType "text")
+                        (eq $displayType "input-single-select") }}
+                            <div class="mb-3">
+                                <div class="input-group {{ if $violations }}has-validation{{ end }}">
+                                    <span data-bs-target="#eiffelRule-{{ $ruleName }}-info" class="input-group-text" role="button" data-bs-toggle="modal">i</span>
+
+                                    {{/* this has to be before the input, otherwise the border radius on the group will not match */}}
+                                    {{ if eq $displayType "input-single-select"}}
+                                        <datalist id="eiffelFormInput-{{ $ruleName }}-datalist">
+                                            {{ range $i, $option := $rule.Value }}
+                                                <option value="{{ $option }}"></option>
+                                            {{ end }}
+                                        </datalist>
+                                    {{ end }}
+
+                                    {{ if eq $displayType "text" }}
+                                        <input type="hidden" name="{{ $inputName }}" value="{{ $rule.Value }}" />
+                                    {{ end }}
+
+                                    <input type="text"
+                                       id="eiffelFormInput-{{ $ruleName }}"
+                                       class="form-control {{ if $violations }}is-invalid{{ end }}"
+                                       name="{{ $inputName }}"
+                                       placeholder="{{ $displayName}}"
+                                       aria-label="{{ $displayName }}"
+                                       aria-description="{{ $rule.Hint }}"
+                                       {{ if eq $displayType "text" }}
+                                           disabled
+                                           value="{{ $rule.Value }}"
+                                       {{ else if $parsingResult }}
+                                           value="{{ index $segments $ruleName }}"
+                                       {{ end }}
+                                       {{ if eq $displayType "input-single-select" }}
+                                           list="eiffelFormInput-{{ $ruleName }}-datalist"
+                                       {{ end }}
+                                       {{ if not $rule.Optional }}
+                                           required
+                                       {{ end }}
+                                    />
+
+                                    {{ if $violations }}
+                                        <div id="eiffelFormInput-{{ $ruleName }}-error" class="invalid-feedback">
+                                            {{ range $i, $violation := $violations }}
+                                                {{ tryTranslate $violation }}
+                                            {{ end }}
+                                        </div>
+                                    {{ end }}
+                                </div>
+                            </div>
+                        {{ else if eq $displayType "input-textarea" }}
+                            <div class="mb-3">
+                                <div class="input-group {{ if $violations }}has-validation{{ end }}">
+                                    <span data-bs-target="#eiffelRule-{{ $ruleName }}-info" class="input-group-text" role="button" data-bs-toggle="modal">i</span>
+
+                                    <textarea id="eiffelFormInput-{{ $ruleName }}"
+                                        class="form-control {{ if $violations }}is-invalid{{ end }}"
+                                        name="{{ $inputName }}"
+                                        placeholder="{{ $displayName }}"
+                                        aria-label="{{ $displayName }}"
+                                        aria-description="{{ $rule.Hint }}"
+                                        {{ if not $rule.Optional }}
+                                            required
+                                        {{ end }}
+                                        rows="1">{{ if not $parsingResult }}{{ $rule.Value }}{{ else }}{{ index $segments $ruleName }}{{ end }}</textarea>
+
+                                    {{ if $violations }}
+                                        <div id="eiffelFormInput-{{ $ruleName }}-error" class="invalid-feedback">
+                                            {{ range $i, $violation := $violations }}
+                                                {{ tryTranslate $violation }}
+                                            {{ end }}
+                                        </div>
+                                    {{ end }}
+                                </div>
+                            </div>
+                        {{ end }}
+
+                        <div class="modal fade" id="eiffelRule-{{ $ruleName }}-info"
+                             tabindex="-1" aria-labelledby="eiffelRule-{{ $ruleName }}-info-label"
+                             aria-hidden="true">
+                            <div class="modal-dialog">
+                                <div class="modal-content">
+                                    <div class="modal-header">
+                                        <h1 class="modal-title fs-5" id="eiffelRule-{{ $ruleName }}-info-label">
+                                            {{ tf "eiffel.elicitation.form.rule-description" "rule" $rule.Name }}
+                                            {{ if $rule.Optional }}{{ t "eiffel.elicitation.form.rule-description.optional-flag" }}{{ end }}
+                                        </h1>
+                                        <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
+                                    </div>
+                                    <div class="modal-body">
+                                        <dl>
+                                            {{ if eq $displayType "text" }}
+                                                <dt>{{ t "eiffel.elicitation.form.value" }}</dt>
+                                                <dd>"{{ $rule.Value }}"</dd>
+                                            {{ end }}
+                                            {{ if eq $displayType "input-single-select" }}
+                                                <dt>{{ t "eiffel.elicitation.form.value-single-select" }}</dt>
+                                                <dd>
+                                                    {{ $valueLength := len $rule.Value }}
+                                                    {{ range $i, $val := $rule.Value }}
+                                                        "{{ $val }}"{{ if lt (add $i 1) $valueLength }}, {{ end }}
+                                                    {{ end }}
+                                                </dd>
+                                            {{ end }}
+                                            {{ if $rule.Hint }}
+                                                <dt>{{ t "eiffel.elicitation.form.hint" }}</dt>
+                                                <dd>{{ $rule.Hint }}</dd>
+                                            {{ end }}
+                                            {{ if $rule.Explanation }}
+                                                <dt>{{ t "eiffel.elicitation.form.explanation" }}</dt>
+                                                <dd>{{ $rule.Explanation }}</dd>
+                                            {{ end }}
+                                            {{ if and (not $rule.Hint) (not $rule.Explanation) }}
+                                                <dd>{{ t "eiffel.elicitation.form.no-further-info" }}</dd>
+                                            {{ end }}
+                                        </dl>
+                                    </div>
+                                    <div class="modal-footer">
+                                        <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ t "harmony.generic.close" }}</button>
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                {{ end }}
+                <div class="col-12">
+                    <button type="submit" class="btn btn-primary w-100">{{ t "eiffel.elicitation.form.submit" }}</button>
+                </div>
+            </div>
+            <div class="row mt-2">
+                {{ range .Data.Successes }}
+                    <div class="col-12">
+                        <div class="alert alert-success" role="alert">{{ t . }}</div>
+                    </div>
+                {{ end }}
+                {{ range .Data.AllViolations }}
+                    <div class="col-12">
+                        <div class="alert alert-danger" role="alert">{{ tryTranslate . }}</div>
+                    </div>
+                {{ end }}
+                {{ range .Data.AllValidationErrors }}
+                    <div class="col-12">
+                        <div class="alert alert-danger" role="alert">{{ t .FieldErrorKey }}</div>
+                    </div>
+                {{ end }}
+
+                {{ if .Data.Form.ParsingResult }}
+                    {{ if not .Data.Form.ParsingResult.Ok }}
+                        <div class="col-12">
+                            <div class="alert alert-danger" role="alert">{{ t "eiffel.elicitation.form.parsing-error" }}</div>
+                        </div>
+                    {{ end }}
+
+                    {{ range .Data.Form.ParsingResult.Warnings }}
+                        <div class="col-12">
+                            <div class="alert alert-warning" role="alert">{{ tryTranslate . }}</div>
+                        </div>
+                    {{ end }}
+
+                    {{ range .Data.Form.ParsingResult.Notices }}
+                        <div class="col-12">
+                            <div class="alert alert-info" role="alert">{{ tryTranslate . }}</div>
+                        </div>
+                    {{ end }}
+                {{ end }}
+            </div>
         </fieldset>
     </form>
 {{ end }}
\ No newline at end of file
diff --git a/templates/eiffel/_form-output-file.go.html b/templates/eiffel/_form-output-file.go.html
index 2e88f8ca9ec1cf80f8275dbbd0b0235245e395e4..4352ac8442e920268b117008a9f61b1a2f581c66 100644
--- a/templates/eiffel/_form-output-file.go.html
+++ b/templates/eiffel/_form-output-file.go.html
@@ -1,3 +1,69 @@
 {{ define "eiffel.elicitation.output-file.form" }}
-    output file: requirements here
+    <div class="alert alert-info" role="alert">
+        {{ t "eiffel.elicitation.output.file.info" }}
+    </div>
+
+    {{ if .Data }}
+        <input form="eiffelElicitationForm" type="hidden" name="elicitationOutputDir" value="{{ .Data.Form.OutputDir }}"/>
+        <input form="eiffelElicitationForm" type="hidden" name="elicitationOutputFile" value="{{ .Data.Form.OutputFile }}"/>
+    {{ end }}
+
+    <div class="eiffel-elicitation-output-file">
+        <form hx-post="/eiffel/elicitation/output/search" hx-trigger="submit"
+              hx-target=".eiffel-requirements" hx-disabled-elt=".eiffel-elicitation-output-file-fieldset"
+              id="eiffelOutputForm">
+            <fieldset class="eiffel-elicitation-output-file-fieldset">
+                {{ if .Data }}
+                    {{ range .Data.Successes }}
+                        <div class="alert alert-success" role="alert">{{ tryTranslate . }}</div>
+                    {{ end }}
+
+                    {{ range .Data.AllViolations }}
+                        <div class="alert alert-danger">{{ tryTranslate . }}</div>
+                    {{ end }}
+                {{ end }}
+
+                <div class="form-floating">
+                    <input id="eiffelOutputDir"
+                       hx-post="/eiffel/elicitation/output/dir/search"
+                       hx-trigger="input changed delay:300ms, search"
+                       hx-target="#eiffelOutputDirList"
+                       hx-disabled-elt="#eiffelOutputDir"
+                       {{ if .Data }}value="{{ .Data.Form.OutputDir }}"{{ end }}
+                       autocomplete="off"
+                       list="eiffelOutputDirList" placeholder="{{ t "eiffel.elicitation.output.directory" }}"
+                       type="text" name="output-dir" class="form-control" aria-describedby="eiffelOutputDirHelp">
+                    <label for="eiffelOutputDir">{{ t "eiffel.elicitation.output.directory" }}</label>
+                    <div id="eiffelOutputDirHelp" class="form-text">
+                        {{ t "eiffel.elicitation.output.directory.help" }}
+                    </div>
+
+                    <datalist id="eiffelOutputDirList">
+                    </datalist>
+                </div>
+
+                <div class="form-floating">
+                    <input id="eiffelOutputFile" required
+                       hx-post="/eiffel/elicitation/output/file/search"
+                       hx-include="#eiffelOutputDir"
+                       hx-trigger="input changed delay:300ms, search"
+                       hx-target="#eiffelOutputFileList"
+                       hx-disabled-elt="#eiffelOutputFile"
+                       {{ if .Data }}value="{{ .Data.Form.OutputFile }}"{{ end }}
+                       autocomplete="off"
+                       list="eiffelOutputFileList" placeholder="{{ t "eiffel.elicitation.output.file" }}"
+                       type="text" name="output-file" class="form-control mt-2" aria-describedby="eiffelOutputFileHelp">
+                    <label for="eiffelOutputFile">{{ t "eiffel.elicitation.output.file" }}</label>
+                    <div id="eiffelOutputFileHelp" class="form-text">
+                        {{ t "eiffel.elicitation.output.file.help" }}
+                    </div>
+
+                    <datalist id="eiffelOutputFileList">
+                    </datalist>
+                </div>
+
+                <input type="submit" class="btn btn-primary mt-3 w-100" value="{{ t "eiffel.elicitation.output.file.save" }}"/>
+            </fieldset>
+        </form>
+    </div>
 {{ end }}
\ No newline at end of file
diff --git a/templates/eiffel/_output-dir-search-result.go.html b/templates/eiffel/_output-dir-search-result.go.html
new file mode 100644
index 0000000000000000000000000000000000000000..dd9e3b7b3675161cb975f259abe4b208b8388d8d
--- /dev/null
+++ b/templates/eiffel/_output-dir-search-result.go.html
@@ -0,0 +1,5 @@
+{{ define "eiffel.elicitation.output.dir.search-result" }}
+    {{ range .Data }}
+        <option value="{{ . }}">{{ . }}</option>
+    {{ end }}
+{{ end }}
\ No newline at end of file
diff --git a/templates/eiffel/_output-file-search-result.go.html b/templates/eiffel/_output-file-search-result.go.html
new file mode 100644
index 0000000000000000000000000000000000000000..db96a13eb6090ef2ffbef2149cd37022061e5f51
--- /dev/null
+++ b/templates/eiffel/_output-file-search-result.go.html
@@ -0,0 +1,5 @@
+{{ define "eiffel.elicitation.output.file.search-result" }}
+    {{ range .Data }}
+        <option value="{{ . }}">{{ . }}</option>
+    {{ end }}
+{{ end }}
\ No newline at end of file
diff --git a/templates/eiffel/elicitation-page.go.html b/templates/eiffel/elicitation-page.go.html
index 4375a0ce7d9ca690c780eaf7d0dbf86456e032a5..809c519b16a73793957bf301d794df57d586c492 100644
--- a/templates/eiffel/elicitation-page.go.html
+++ b/templates/eiffel/elicitation-page.go.html
@@ -2,14 +2,6 @@
     {{ template "index" . }}
 {{ end }}
 
-{{ block "content-container" . }}
-    <section class="section content-section mt-3">
-        <div class="content-container container content">
-            {{ template "content" . }}
-        </div>
-    </section>
-{{ end }}
-
 {{ define "content" }}
     {{ template "eiffel.elicitation" . }}
 {{ end }}
@@ -62,7 +54,7 @@
         </div>
 
         <div class="col eiffel-requirements">
-            <p>Requirements here</p>
+            {{ template "eiffel.elicitation.output-file.form" .Data.Form.OutputFormData }}
         </div>
     </div>
 {{ end }}
\ No newline at end of file
diff --git a/templates/home.go.html b/templates/home.go.html
index d43dd4e35c18355fe640497d1c413f3581ba07e3..99d3f025a0370bcf71cf940e3ecbb41f50f54a9d 100644
--- a/templates/home.go.html
+++ b/templates/home.go.html
@@ -3,11 +3,21 @@
 {{ end }}
 
 {{ define "content" }}
-    <h1>{{ t "harmony.head.welcome" }}</h1>
+    <div class="content-inner col-7 m-auto">
+        <h1>{{ t "harmony.head.welcome" }}</h1>
 
-    {{ t "harmony.text.welcome" | safeHTML }}
+        {{ t "harmony.text.welcome" | safeHTML }}
 
-    <div class="d-grid">
-        <a href="/auth/login" hx-boost="true" hx-target="body" hx-swap="innerHTML" class="btn btn-primary">{{ t "user.auth.login.action" }}</a>
+        <div class="d-grid">
+            {{ if not (index .Extra "User") }}
+                <a href="/auth/login" hx-boost="true" hx-target="body" hx-swap="innerHTML" class="btn btn-primary">
+                    {{ t "user.auth.login.action" }}
+                </a>
+            {{ else }}
+                <a href="/eiffel" hx-boost="true" hx-target="body" hx-swap="innerHTML" class="btn btn-primary">
+                    {{ t "eiffel.elicitation.call-to-action" }}
+                </a>
+            {{ end }}
+        </div>
     </div>
 {{ end }}
\ No newline at end of file
diff --git a/templates/template/_form.go.html b/templates/template/_form.go.html
index 054028dd8f651f82db8a53d4278bc6db4e6fdece..de366a14c9fb6ead0deaa44a302540ac89321fd5 100644
--- a/templates/template/_form.go.html
+++ b/templates/template/_form.go.html
@@ -30,8 +30,8 @@
                         {{ range .Data.AllValidationErrors }}
                             <div class="alert alert-danger">{{ t .FieldErrorKey }}</div>
                         {{ end }}
-                        {{ range tErrs .Data.AllTranslatableErrors }}
-                            <div class="alert alert-danger">{{ . }}</div>
+                        {{ range .Data.AllViolations }}
+                            <div class="alert alert-danger">{{ tryTranslate . }}</div>
                         {{ end }}
                         {{ range .Data.Successes }}
                             <div class="alert alert-success">{{ t . }}</div>
diff --git a/templates/user/auth/login.go.html b/templates/user/auth/login.go.html
index c048bac087b39705f870247a66dce1485a5841d5..4e55c9c4412c9f4e3413a347a6222adcac6cd880 100644
--- a/templates/user/auth/login.go.html
+++ b/templates/user/auth/login.go.html
@@ -3,7 +3,7 @@
 {{ end }}
 
 {{ define "content" }}
-    <div class="card auth-login-providers">
+    <div class="card auth-login-providers col-6 m-auto">
         <div class="card-header">{{ t "user.auth.login.title" }}</div>
         <div class="card-body">
             {{ block "auth.login.providers" . }}
diff --git a/translations/de.json b/translations/de.json
index ae5ecc6b1068d6d73c757de787a4ee4135bc1b63..c7a68d307430783d053e28de61bf063a696168e5 100644
--- a/translations/de.json
+++ b/translations/de.json
@@ -94,9 +94,39 @@
         "invalid-rule-value": "Der Wert \"value\" für die Regel \"{{ .rule }}\" vom Typ \"{{ .type }}\" ist ungültig. Bitte überprüfen Sie die Schablonen-Dokumentation.",
         "not-a-slice": "Der Wert \"value\" für die Regel \"{{ .rule }}\" vom Typ \"{{ .type }}\" ist keine Liste. Bitte überprüfen Sie die Schablonen-Dokumentation.",
         "not-a-string": "Der Wert \"value\" für die Regel \"{{ .rule }}\" vom Typ \"{{ .type }}\" sollte aus einer Zeichenkette oder einer Liste an Zeichenketten bestehen, jedoch wurde ein anderer Typ gefunden. Bitte überprüfen Sie die Schablonen-Dokumentation."
-      }
+      },
+      "equals.error": "Erwarteter Wert: {{ .expected }}.",
+      "equals-any.error": "Erwarteter Wert: {{ .expected }}."
     },
     "elicitation": {
+      "call-to-action": "Anforderungen mit EIFFEL erfassen",
+      "parse": {
+        "flawless-success": "Die Anforderung ist fehlerfrei.",
+        "success": "Die Anforderung ist gültig, jedoch wurden potentielle Probleme gefunden."
+      },
+      "form": {
+        "title": "Anforderung erfassen",
+        "submit": "Anforderung prüfen",
+        "parsing-error": "Die Anforderung entspricht nicht der Schablone.",
+        "rule-description": "{{ .rule }}",
+        "rule-description.optional-flag": "(Optional)",
+        "hint": "Hinweis",
+        "explanation": "Erklärung",
+        "no-further-info": "Es wurden keine weiteren Informationen für diese Regel hinterlegt.",
+        "value": "Erwartet",
+        "value-single-select": "Ein Wert aus"
+      },
+      "output": {
+        "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",
@@ -115,9 +145,6 @@
           "not-found": "Die Variante wurde nicht gefunden.",
           "description": "Variantenbeschreibung"
         },
-        "form": {
-          "title": "Anforderung erfassen"
-        },
         "construction": "Schablonenaufbau",
         "example": "Beispielsatz",
         "description.title": "Beschreibung",