diff --git a/docs/memory/component-registry.md b/docs/memory/component-registry.md
index 9f46a7d..ff8bfec 100644
--- a/docs/memory/component-registry.md
+++ b/docs/memory/component-registry.md
@@ -49,11 +49,11 @@ duplicates) and MUST update it after completing one.
| Component | Status | Composed of | Notes |
|-----------|--------|-------------|-------|
-| ServiceSelector | review | ServiceOption × n + Typography + Button | Single-select service panel for arrangement flow. Heading + subheading + ServiceOption list (radiogroup) + optional continue Button. Manages selection state via selectedId/onSelect. maxDescriptionLines pass-through. |
+| ServiceSelector | done | ServiceOption × n + Typography + Button | Single-select service panel for arrangement flow. Heading + subheading + ServiceOption list (radiogroup) + optional continue Button. Manages selection state via selectedId/onSelect. maxDescriptionLines pass-through. |
| PricingTable | planned | PriceCard × n + Typography | Comparative pricing display |
-| ArrangementForm | planned | FormField × n + StepIndicator + Button | Multi-step arrangement flow |
+| ArrangementForm | review | StepIndicator + ServiceSelector + AddOnOption + Button + Typography | Multi-step arrangement wizard. Controlled by parent (currentStep/onNext/onBack). Each step renders arbitrary content with consistent nav buttons. canContinue per step. Back/Continue/Complete labels configurable. |
| Navigation | done | AppBar + Link + IconButton + Button + Divider + Drawer | Responsive site header. Desktop: logo left, links right, optional CTA. Mobile: hamburger + drawer with nav items, CTA, help footer. Sticky, grey surface bg (surface.subtle). Real FA logo from brandassets/. Maps to Figma Main Nav (14:108) + Mobile Header (2391:41508). |
-| Footer | review | Link × n + Typography + Divider + Container + Grid | Dark espresso (brand.950) site footer. Logo + tagline + contact (phone/email) + link group columns + legal bar. Semantic HTML (footer, nav, ul). Critique: 38/40 (Excellent). |
+| Footer | done | Link × n + Typography + Divider + Container + Grid | Dark espresso (brand.950) site footer. Logo + tagline + contact (phone/email) + link group columns + legal bar. Semantic HTML (footer, nav, ul). Critique: 38/40 (Excellent). |
## Future enhancements
diff --git a/src/components/organisms/ArrangementForm/ArrangementForm.stories.tsx b/src/components/organisms/ArrangementForm/ArrangementForm.stories.tsx
new file mode 100644
index 0000000..62719a9
--- /dev/null
+++ b/src/components/organisms/ArrangementForm/ArrangementForm.stories.tsx
@@ -0,0 +1,380 @@
+import { useState } from 'react';
+import type { Meta, StoryObj } from '@storybook/react';
+import Box from '@mui/material/Box';
+import { ArrangementForm } from './ArrangementForm';
+import { ServiceSelector } from '../ServiceSelector';
+import { AddOnOption } from '../../molecules/AddOnOption';
+import { Typography } from '../../atoms/Typography';
+import { Navigation } from '../Navigation';
+
+// ─── Shared data ─────────────────────────────────────────────────────────────
+
+const serviceTypes = [
+ { id: 'burial', name: 'Traditional Burial', price: 4200, description: 'Full service with chapel ceremony, viewing, hearse, and graveside committal.' },
+ { id: 'cremation', name: 'Cremation with Service', price: 2800, description: 'Chapel ceremony followed by cremation. Ashes returned in a standard urn.' },
+ { id: 'direct-cremation', name: 'Direct Cremation', price: 1600, description: 'Simple cremation without a formal service. Ashes returned within 5 business days.' },
+];
+
+const coffinOptions = [
+ { id: 'eco', name: 'Eco Willow', price: 850, description: 'Handwoven natural willow. Biodegradable and sustainable.' },
+ { id: 'classic', name: 'Classic Maple', price: 1400, description: 'Solid maple with satin finish and brass handles.' },
+ { id: 'premium', name: 'Premium Oak', price: 2200, description: 'Quarter-sawn oak with high-gloss lacquer and gold-plated handles.' },
+];
+
+const addOns = [
+ { id: 'flowers', name: 'Floral Arrangements', price: 450, description: 'Seasonal flowers for the chapel and casket spray.' },
+ { id: 'video', name: 'Memorial Video', price: 350, description: 'Professional video tribute with photos and music, played during the service.' },
+ { id: 'catering', name: 'Wake Catering', price: 800, description: 'Light refreshments for up to 50 guests after the service.' },
+ { id: 'transport', name: 'Family Limousine', price: 300, description: 'Luxury vehicle for immediate family to and from the service.' },
+ { id: 'death-notice', name: 'Newspaper Death Notice', price: 180, description: 'Published in one major metropolitan newspaper of your choice.' },
+];
+
+const FALogoNav = () => (
+
+);
+
+// ─── Meta ────────────────────────────────────────────────────────────────────
+
+const meta: Meta = {
+ title: 'Organisms/ArrangementForm',
+ component: ArrangementForm,
+ tags: ['autodocs'],
+ parameters: {
+ layout: 'centered',
+ },
+ decorators: [
+ (Story) => (
+
+
+
+ ),
+ ],
+};
+
+export default meta;
+type Story = StoryObj;
+
+// --- Default (Static) --------------------------------------------------------
+
+/** Static view at step 1 — shows structure without interactivity */
+export const Default: Story = {
+ args: {
+ heading: 'Plan your arrangement',
+ subheading: 'We\'ll guide you through each step. You can go back and change your selections at any time.',
+ steps: [
+ { label: 'Service', content: Service selection content },
+ { label: 'Coffin', content: Coffin selection content },
+ { label: 'Extras', content: Optional extras content },
+ { label: 'Review', content: Review summary content },
+ ],
+ currentStep: 0,
+ },
+};
+
+// --- Interactive Full Flow ---------------------------------------------------
+
+/** Complete 4-step arrangement flow with real components */
+export const InteractiveFlow: Story = {
+ render: () => {
+ const [step, setStep] = useState(0);
+ const [serviceId, setServiceId] = useState();
+ const [coffinId, setCoffinId] = useState();
+ const [selectedAddOns, setSelectedAddOns] = useState>({});
+
+ const toggleAddOn = (id: string, checked: boolean) => {
+ setSelectedAddOns((prev) => ({ ...prev, [id]: checked }));
+ };
+
+ const getServiceName = () => serviceTypes.find((s) => s.id === serviceId)?.name ?? '—';
+ const getServicePrice = () => serviceTypes.find((s) => s.id === serviceId)?.price ?? 0;
+ const getCoffinName = () => coffinOptions.find((c) => c.id === coffinId)?.name ?? '—';
+ const getCoffinPrice = () => coffinOptions.find((c) => c.id === coffinId)?.price ?? 0;
+ const getAddOnTotal = () =>
+ addOns.filter((a) => selectedAddOns[a.id]).reduce((sum, a) => sum + a.price, 0);
+ const getTotal = () => getServicePrice() + getCoffinPrice() + getAddOnTotal();
+
+ const steps = [
+ {
+ label: 'Service',
+ canContinue: !!serviceId,
+ content: (
+
+ ),
+ },
+ {
+ label: 'Coffin',
+ canContinue: !!coffinId,
+ content: (
+
+ ),
+ },
+ {
+ label: 'Extras',
+ canContinue: true,
+ content: (
+
+
+ Optional extras
+
+
+ Add any optional services. You can skip this step if none are needed.
+
+
+ {addOns.map((addOn) => (
+ toggleAddOn(addOn.id, checked)}
+ maxDescriptionLines={1}
+ />
+ ))}
+
+
+ ),
+ },
+ {
+ label: 'Review',
+ canContinue: true,
+ content: (
+
+
+ Review your arrangement
+
+
+ {/* Service */}
+
+
+ Service
+
+
+ {getServiceName()}
+
+ ${getServicePrice().toLocaleString('en-AU')}
+
+
+
+
+ {/* Coffin */}
+
+
+ Coffin
+
+
+ {getCoffinName()}
+
+ ${getCoffinPrice().toLocaleString('en-AU')}
+
+
+
+
+ {/* Extras */}
+ {addOns.filter((a) => selectedAddOns[a.id]).length > 0 && (
+
+
+ Extras
+
+ {addOns
+ .filter((a) => selectedAddOns[a.id])
+ .map((addOn) => (
+
+ {addOn.name}
+
+ ${addOn.price.toLocaleString('en-AU')}
+
+
+ ))}
+
+ )}
+
+ {/* Total */}
+
+ Estimated total
+
+ ${getTotal().toLocaleString('en-AU')}
+
+
+
+
+ This is an estimate only. Final pricing will be confirmed by your chosen funeral director.
+
+
+ ),
+ },
+ ];
+
+ return (
+ setStep((s) => Math.min(s + 1, steps.length - 1))}
+ onBack={() => setStep((s) => Math.max(s - 1, 0))}
+ onComplete={() => alert(`Arrangement complete! Total: $${getTotal().toLocaleString('en-AU')}`)}
+ />
+ );
+ },
+};
+
+// --- Mid-Flow ----------------------------------------------------------------
+
+/** Starts at step 2 with prior selections visible */
+export const MidFlow: Story = {
+ args: {
+ steps: [
+ { label: 'Service', content: Completed },
+ { label: 'Coffin', content: Completed },
+ {
+ label: 'Extras',
+ content: (
+
+
+ Optional extras
+
+
+ Add any optional services you'd like.
+
+
+ {addOns.slice(0, 3).map((addOn) => (
+
+ ))}
+
+
+ ),
+ },
+ { label: 'Review', content: Review step },
+ ],
+ currentStep: 2,
+ },
+};
+
+// --- Two Steps ---------------------------------------------------------------
+
+/** Simplified 2-step flow for simpler arrangements */
+export const TwoSteps: Story = {
+ render: () => {
+ const [step, setStep] = useState(0);
+ const [serviceId, setServiceId] = useState();
+
+ return (
+
+ ),
+ },
+ {
+ label: 'Review',
+ content: (
+
+
+ Confirm your selection
+
+
+ {serviceTypes.find((s) => s.id === serviceId)?.name ?? 'Nothing selected'}
+
+
+ ),
+ },
+ ]}
+ currentStep={step}
+ onNext={() => setStep(1)}
+ onBack={() => setStep(0)}
+ onComplete={() => alert('Complete!')}
+ completeLabel="Confirm arrangement"
+ />
+ );
+ },
+};
+
+// --- In Page Context ---------------------------------------------------------
+
+/** Full page with Navigation wrapping the arrangement flow */
+export const InPageContext: Story = {
+ decorators: [
+ (Story) => (
+
+
+
+ ),
+ ],
+ render: () => {
+ const [step, setStep] = useState(0);
+ const [serviceId, setServiceId] = useState();
+
+ return (
+
+ }
+ items={[
+ { label: 'FAQ', href: '/faq' },
+ { label: 'Contact Us', href: '/contact' },
+ ]}
+ />
+
+
+ ),
+ },
+ { label: 'Coffin', content: Coffin step... },
+ { label: 'Extras', content: Extras step... },
+ { label: 'Review', content: Review step... },
+ ]}
+ currentStep={step}
+ onNext={() => setStep((s) => s + 1)}
+ onBack={() => setStep((s) => s - 1)}
+ onComplete={() => alert('Done!')}
+ />
+
+
+ );
+ },
+};
diff --git a/src/components/organisms/ArrangementForm/ArrangementForm.tsx b/src/components/organisms/ArrangementForm/ArrangementForm.tsx
new file mode 100644
index 0000000..7969ef8
--- /dev/null
+++ b/src/components/organisms/ArrangementForm/ArrangementForm.tsx
@@ -0,0 +1,174 @@
+import React from 'react';
+import Box from '@mui/material/Box';
+import type { SxProps, Theme } from '@mui/material/styles';
+import { Typography } from '../../atoms/Typography';
+import { Button } from '../../atoms/Button';
+import { StepIndicator } from '../../molecules/StepIndicator';
+
+// ─── Types ───────────────────────────────────────────────────────────────────
+
+/** A single step in the arrangement flow */
+export interface ArrangementStep {
+ /** Step label shown in the StepIndicator */
+ label: string;
+ /** Step content — rendered when this step is active */
+ content: React.ReactNode;
+ /** Whether the user can proceed past this step. Defaults to true. */
+ canContinue?: boolean;
+}
+
+/** Props for the FA ArrangementForm organism */
+export interface ArrangementFormProps {
+ /** The steps in the arrangement flow */
+ steps: ArrangementStep[];
+ /** Current step index (0-based). Controlled by parent. */
+ currentStep: number;
+ /** Called when the user advances to the next step */
+ onNext?: () => void;
+ /** Called when the user goes back to the previous step */
+ onBack?: () => void;
+ /** Called when the user completes the final step */
+ onComplete?: () => void;
+ /** Label for the next button — defaults to "Continue" */
+ nextLabel?: string;
+ /** Label for the back button — defaults to "Back" */
+ backLabel?: string;
+ /** Label for the final step's button — defaults to "Review arrangement" */
+ completeLabel?: string;
+ /** Optional heading above the step content */
+ heading?: string;
+ /** Optional subheading below the heading */
+ subheading?: string;
+ /** MUI sx prop for the root element */
+ sx?: SxProps;
+}
+
+// ─── Component ───────────────────────────────────────────────────────────────
+
+/**
+ * Multi-step arrangement form for the FA design system.
+ *
+ * The core planning flow — guides users through service selection,
+ * coffin choice, venue, extras, and review. Each step renders
+ * arbitrary content (ServiceSelector, AddOnOption lists, forms, etc.)
+ * with consistent navigation and progress indication.
+ *
+ * Composes StepIndicator + Typography + Button + step content.
+ *
+ * State is controlled by the parent — the form doesn't own step
+ * state or selection state. This keeps it composable and testable.
+ *
+ * Usage:
+ * ```tsx
+ * , canContinue: !!selected },
+ * { label: 'Coffin', content: },
+ * { label: 'Extras', content: },
+ * { label: 'Review', content: },
+ * ]}
+ * currentStep={step}
+ * onNext={() => setStep(s => s + 1)}
+ * onBack={() => setStep(s => s - 1)}
+ * onComplete={() => submit()}
+ * />
+ * ```
+ */
+export const ArrangementForm = React.forwardRef(
+ (
+ {
+ steps,
+ currentStep,
+ onNext,
+ onBack,
+ onComplete,
+ nextLabel = 'Continue',
+ backLabel = 'Back',
+ completeLabel = 'Review arrangement',
+ heading,
+ subheading,
+ sx,
+ },
+ ref,
+ ) => {
+ const isFirstStep = currentStep === 0;
+ const isLastStep = currentStep === steps.length - 1;
+ const activeStep = steps[currentStep];
+ const canContinue = activeStep?.canContinue ?? true;
+
+ const stepLabels = steps.map((s) => ({ label: s.label }));
+
+ return (
+
+ {/* Progress indicator */}
+
+
+ {/* Optional heading */}
+ {heading && (
+
+
+ {heading}
+
+ {subheading && (
+
+ {subheading}
+
+ )}
+
+ )}
+
+ {/* Step content */}
+
+ {activeStep?.content}
+
+
+ {/* Navigation buttons */}
+
+ {!isFirstStep ? (
+
+ ) : (
+
+ )}
+
+
+
+
+ );
+ },
+);
+
+ArrangementForm.displayName = 'ArrangementForm';
+export default ArrangementForm;
diff --git a/src/components/organisms/ArrangementForm/index.ts b/src/components/organisms/ArrangementForm/index.ts
new file mode 100644
index 0000000..2390bfe
--- /dev/null
+++ b/src/components/organisms/ArrangementForm/index.ts
@@ -0,0 +1 @@
+export { ArrangementForm, type ArrangementFormProps, type ArrangementStep } from './ArrangementForm';