diff --git a/frontend/src/lib/recipes/IngredientList.svelte b/frontend/src/lib/recipes/IngredientList.svelte
new file mode 100644
index 0000000..3a15099
--- /dev/null
+++ b/frontend/src/lib/recipes/IngredientList.svelte
@@ -0,0 +1,32 @@
+
+
+
+
+ Zutaten
+
+
+
+ {#each ingredients as ingredient (ingredient.ingredientId ?? ingredient.name)}
+ -
+ {#if ingredient.quantity != null}
+
+ {ingredient.quantity}{ingredient.unit != null ? ` ${ingredient.unit}` : ''}
+
+ {/if}
+ {ingredient.name}
+
+ {/each}
+
+
diff --git a/frontend/src/lib/recipes/IngredientList.test.ts b/frontend/src/lib/recipes/IngredientList.test.ts
new file mode 100644
index 0000000..927348a
--- /dev/null
+++ b/frontend/src/lib/recipes/IngredientList.test.ts
@@ -0,0 +1,44 @@
+import { describe, it, expect } from 'vitest';
+import { render, screen } from '@testing-library/svelte';
+import IngredientList from './IngredientList.svelte';
+
+const mockIngredients = [
+ { ingredientId: 'i1', name: 'Spaghetti', quantity: 200, unit: 'g' },
+ { ingredientId: 'i2', name: 'Hackfleisch', quantity: 400, unit: 'g' },
+ { ingredientId: 'i3', name: 'Salz', quantity: undefined, unit: undefined }
+];
+
+describe('IngredientList', () => {
+ it('renders the section heading', () => {
+ render(IngredientList, { props: { ingredients: mockIngredients } });
+ expect(screen.getByText(/zutaten/i)).toBeInTheDocument();
+ });
+
+ it('renders a row for each ingredient', () => {
+ render(IngredientList, { props: { ingredients: mockIngredients } });
+ expect(screen.getByText('Spaghetti')).toBeInTheDocument();
+ expect(screen.getByText('Hackfleisch')).toBeInTheDocument();
+ expect(screen.getByText('Salz')).toBeInTheDocument();
+ });
+
+ it('renders quantity and unit when present', () => {
+ render(IngredientList, { props: { ingredients: mockIngredients } });
+ expect(screen.getByText('200 g')).toBeInTheDocument();
+ expect(screen.getByText('400 g')).toBeInTheDocument();
+ });
+
+ it('renders no quantity when not present', () => {
+ render(IngredientList, { props: { ingredients: mockIngredients } });
+ expect(screen.queryByText('undefined')).not.toBeInTheDocument();
+ });
+
+ it('has no remove buttons (read-only)', () => {
+ render(IngredientList, { props: { ingredients: mockIngredients } });
+ expect(screen.queryByRole('button')).not.toBeInTheDocument();
+ });
+
+ it('renders empty state when ingredients array is empty', () => {
+ render(IngredientList, { props: { ingredients: [] } });
+ expect(screen.getByText(/zutaten/i)).toBeInTheDocument();
+ });
+});