diff --git a/src/components/pages/CemeteryStep/CemeteryStep.stories.tsx b/src/components/pages/CemeteryStep/CemeteryStep.stories.tsx
new file mode 100644
index 0000000..6518eca
--- /dev/null
+++ b/src/components/pages/CemeteryStep/CemeteryStep.stories.tsx
@@ -0,0 +1,243 @@
+import { useState } from 'react';
+import type { Meta, StoryObj } from '@storybook/react';
+import { CemeteryStep } from './CemeteryStep';
+import type { CemeteryStepValues, CemeteryStepErrors, Cemetery } from './CemeteryStep';
+import { Navigation } from '../../organisms/Navigation';
+import Box from '@mui/material/Box';
+
+// ─── Helpers ─────────────────────────────────────────────────────────────────
+
+const FALogo = () => (
+
+
+
+
+);
+
+const nav = (
+ }
+ items={[
+ { label: 'FAQ', href: '/faq' },
+ { label: 'Contact Us', href: '/contact' },
+ ]}
+ />
+);
+
+const sampleCemeteries: Cemetery[] = [
+ {
+ id: 'rookwood',
+ name: 'Rookwood Cemetery',
+ location: 'Lidcombe, NSW',
+ price: 4500,
+ },
+ {
+ id: 'northern-suburbs',
+ name: 'Northern Suburbs Memorial Gardens',
+ location: 'North Ryde, NSW',
+ price: 5200,
+ },
+ {
+ id: 'macquarie-park',
+ name: 'Macquarie Park Cemetery',
+ location: 'Macquarie Park, NSW',
+ price: 4800,
+ },
+];
+
+const defaultValues: CemeteryStepValues = {
+ burialOwn: null,
+ burialCustom: null,
+ selectedCemeteryId: null,
+};
+
+// ─── Meta ────────────────────────────────────────────────────────────────────
+
+const meta: Meta = {
+ title: 'Pages/CemeteryStep',
+ component: CemeteryStep,
+ tags: ['autodocs'],
+ parameters: {
+ layout: 'fullscreen',
+ },
+};
+
+export default meta;
+type Story = StoryObj;
+
+// ─── Interactive (default) ──────────────────────────────────────────────────
+
+/** Fully interactive — progressive disclosure flow */
+export const Default: Story = {
+ render: () => {
+ const [values, setValues] = useState({ ...defaultValues });
+ const [errors, setErrors] = useState({});
+
+ const handleContinue = () => {
+ const newErrors: CemeteryStepErrors = {};
+ if (!values.burialOwn) newErrors.burialOwn = 'Please let us know about the burial plot.';
+ if (values.burialOwn === 'no' && !values.burialCustom)
+ newErrors.burialCustom = "Please let us know if you'd like to choose a specific cemetery.";
+ if (values.burialOwn === 'no' && values.burialCustom === 'yes' && !values.selectedCemeteryId)
+ newErrors.selectedCemeteryId = 'Please choose a cemetery.';
+ setErrors(newErrors);
+ if (Object.keys(newErrors).length === 0) alert('Continue');
+ };
+
+ return (
+ {
+ setValues(v);
+ setErrors({});
+ }}
+ onContinue={handleContinue}
+ onBack={() => alert('Back')}
+ onSaveAndExit={() => alert('Save')}
+ errors={errors}
+ cemeteries={sampleCemeteries}
+ navigation={nav}
+ />
+ );
+ },
+};
+
+// ─── Has existing plot ──────────────────────────────────────────────────────
+
+/** User already owns a burial plot — short confirmation */
+export const HasExistingPlot: Story = {
+ render: () => {
+ const [values, setValues] = useState({
+ ...defaultValues,
+ burialOwn: 'yes',
+ });
+ return (
+ alert('Continue')}
+ onBack={() => alert('Back')}
+ cemeteries={sampleCemeteries}
+ navigation={nav}
+ />
+ );
+ },
+};
+
+// ─── Provider arranges ──────────────────────────────────────────────────────
+
+/** User wants provider to arrange — no cemetery grid */
+export const ProviderArranges: Story = {
+ render: () => {
+ const [values, setValues] = useState({
+ ...defaultValues,
+ burialOwn: 'no',
+ burialCustom: 'no',
+ });
+ return (
+ alert('Continue')}
+ onBack={() => alert('Back')}
+ cemeteries={sampleCemeteries}
+ navigation={nav}
+ />
+ );
+ },
+};
+
+// ─── Cemetery grid visible ──────────────────────────────────────────────────
+
+/** User wants to choose — cemetery grid revealed */
+export const CemeteryGridVisible: Story = {
+ render: () => {
+ const [values, setValues] = useState({
+ ...defaultValues,
+ burialOwn: 'no',
+ burialCustom: 'yes',
+ });
+ return (
+ alert('Continue')}
+ onBack={() => alert('Back')}
+ cemeteries={sampleCemeteries}
+ navigation={nav}
+ />
+ );
+ },
+};
+
+// ─── Cemetery selected ──────────────────────────────────────────────────────
+
+/** Cemetery selected */
+export const CemeterySelected: Story = {
+ render: () => {
+ const [values, setValues] = useState({
+ burialOwn: 'no',
+ burialCustom: 'yes',
+ selectedCemeteryId: 'rookwood',
+ });
+ return (
+ alert('Continue')}
+ onBack={() => alert('Back')}
+ cemeteries={sampleCemeteries}
+ navigation={nav}
+ />
+ );
+ },
+};
+
+// ─── Pre-planning ───────────────────────────────────────────────────────────
+
+/** Pre-planning variant */
+export const PrePlanning: Story = {
+ render: () => {
+ const [values, setValues] = useState({ ...defaultValues });
+ return (
+ alert('Continue')}
+ onBack={() => alert('Back')}
+ cemeteries={sampleCemeteries}
+ isPrePlanning
+ navigation={nav}
+ />
+ );
+ },
+};
+
+// ─── Validation errors ──────────────────────────────────────────────────────
+
+/** All errors showing */
+export const WithErrors: Story = {
+ render: () => {
+ const [values, setValues] = useState({ ...defaultValues });
+ return (
+ {}}
+ errors={{ burialOwn: 'Please let us know about the burial plot.' }}
+ cemeteries={sampleCemeteries}
+ navigation={nav}
+ />
+ );
+ },
+};
diff --git a/src/components/pages/CemeteryStep/CemeteryStep.tsx b/src/components/pages/CemeteryStep/CemeteryStep.tsx
new file mode 100644
index 0000000..f5d8dbd
--- /dev/null
+++ b/src/components/pages/CemeteryStep/CemeteryStep.tsx
@@ -0,0 +1,296 @@
+import React from 'react';
+import Box from '@mui/material/Box';
+import FormControl from '@mui/material/FormControl';
+import FormLabel from '@mui/material/FormLabel';
+import FormControlLabel from '@mui/material/FormControlLabel';
+import RadioGroup from '@mui/material/RadioGroup';
+import Radio from '@mui/material/Radio';
+import type { SxProps, Theme } from '@mui/material/styles';
+import { WizardLayout } from '../../templates/WizardLayout';
+import { Card } from '../../atoms/Card';
+import { Collapse } from '../../atoms/Collapse';
+import { Typography } from '../../atoms/Typography';
+import { Button } from '../../atoms/Button';
+import { Divider } from '../../atoms/Divider';
+
+// ─── Types ───────────────────────────────────────────────────────────────────
+
+/** A cemetery available for selection */
+export interface Cemetery {
+ id: string;
+ name: string;
+ location: string;
+ price?: number;
+}
+
+/** Form values for the cemetery step */
+export interface CemeteryStepValues {
+ /** Does the family already own a burial plot? */
+ burialOwn: 'yes' | 'no' | null;
+ /** Would they like to choose a specific cemetery? (when burialOwn=no) */
+ burialCustom: 'yes' | 'no' | null;
+ /** Selected cemetery ID */
+ selectedCemeteryId: string | null;
+}
+
+/** Field-level error messages */
+export interface CemeteryStepErrors {
+ burialOwn?: string;
+ burialCustom?: string;
+ selectedCemeteryId?: string;
+}
+
+/** Props for the CemeteryStep page component */
+export interface CemeteryStepProps {
+ /** Current form values */
+ values: CemeteryStepValues;
+ /** Callback when any field value changes */
+ onChange: (values: CemeteryStepValues) => void;
+ /** Callback when the Continue button is clicked */
+ onContinue: () => void;
+ /** Callback for back navigation */
+ onBack?: () => void;
+ /** Callback for save-and-exit */
+ onSaveAndExit?: () => void;
+ /** Field-level validation errors */
+ errors?: CemeteryStepErrors;
+ /** Whether the Continue button is in a loading state */
+ loading?: boolean;
+ /** Available cemeteries */
+ cemeteries: Cemetery[];
+ /** Whether this is a pre-planning flow */
+ isPrePlanning?: boolean;
+ /** Navigation bar */
+ navigation?: React.ReactNode;
+ /** Progress stepper */
+ progressStepper?: React.ReactNode;
+ /** Running total */
+ runningTotal?: React.ReactNode;
+ /** Hide the help bar */
+ hideHelpBar?: boolean;
+ /** MUI sx prop */
+ sx?: SxProps;
+}
+
+// ─── Component ───────────────────────────────────────────────────────────────
+
+/**
+ * Step 9 — Cemetery for the FA arrangement wizard.
+ *
+ * Cemetery selection and burial plot preferences. Only shown for
+ * burial-type funerals (Service & Burial, Graveside, Burial Only).
+ *
+ * Progressive disclosure flow:
+ * 1. "Do you have a burial plot?" → Yes/No
+ * 2. If No: "Would you like to choose a specific cemetery?" → Yes/No
+ * 3. If Yes to #2: Cemetery card grid
+ *
+ * If the user already owns a plot, the cemetery grid can be shown
+ * for confirmation (passed via showGridForExistingPlot).
+ *
+ * Pure presentation component — props in, callbacks out.
+ *
+ * Spec: documentation/steps/steps/09_cemetery.yaml
+ */
+export const CemeteryStep: React.FC = ({
+ values,
+ onChange,
+ onContinue,
+ onBack,
+ onSaveAndExit,
+ errors,
+ loading = false,
+ cemeteries,
+ isPrePlanning = false,
+ navigation,
+ progressStepper,
+ runningTotal,
+ hideHelpBar,
+ sx,
+}) => {
+ const showCustomQuestion = values.burialOwn === 'no';
+ const showCemeteryGrid = values.burialOwn === 'no' && values.burialCustom === 'yes';
+
+ const handleBurialOwnChange = (value: string) => {
+ onChange({
+ ...values,
+ burialOwn: value as CemeteryStepValues['burialOwn'],
+ // Reset dependent fields when parent changes
+ burialCustom: null,
+ selectedCemeteryId: null,
+ });
+ };
+
+ const handleBurialCustomChange = (value: string) => {
+ onChange({
+ ...values,
+ burialCustom: value as CemeteryStepValues['burialCustom'],
+ selectedCemeteryId: null,
+ });
+ };
+
+ const handleCemeterySelect = (id: string) => {
+ onChange({ ...values, selectedCemeteryId: id });
+ };
+
+ return (
+
+ {/* Page heading */}
+
+ Cemetery
+
+
+
+ {isPrePlanning
+ ? "If you haven't decided on a cemetery yet, the funeral provider can help with this later."
+ : 'Choose where the burial will take place.'}
+
+
+ {
+ e.preventDefault();
+ onContinue();
+ }}
+ >
+ {/* ─── Burial plot question ─── */}
+
+
+ Do you already have a burial plot?
+
+ handleBurialOwnChange(e.target.value)}
+ >
+ } label="Yes, we have a plot" />
+ } label="No, we need to find one" />
+
+ {errors?.burialOwn && (
+
+ {errors.burialOwn}
+
+ )}
+
+
+ {/* ─── Custom cemetery question (progressive disclosure) ─── */}
+
+
+
+ Would you like to choose a specific cemetery?
+
+ handleBurialCustomChange(e.target.value)}
+ >
+ } label="Yes, I'd like to choose" />
+ }
+ label="No, the funeral provider can arrange this"
+ />
+
+ {errors?.burialCustom && (
+
+ {errors.burialCustom}
+
+ )}
+
+
+
+ {/* ─── Cemetery card grid (progressive disclosure) ─── */}
+
+
+
+ Available cemeteries
+
+
+
+ {cemeteries.map((cemetery, index) => (
+ handleCemeterySelect(cemetery.id)}
+ role="radio"
+ aria-checked={cemetery.id === values.selectedCemeteryId}
+ tabIndex={
+ values.selectedCemeteryId === null
+ ? index === 0
+ ? 0
+ : -1
+ : cemetery.id === values.selectedCemeteryId
+ ? 0
+ : -1
+ }
+ sx={{ p: 3 }}
+ >
+ {cemetery.name}
+
+ {cemetery.location}
+
+ {cemetery.price != null && (
+
+ ${cemetery.price.toLocaleString('en-AU')}
+
+ )}
+
+ ))}
+
+
+ {errors?.selectedCemeteryId && (
+
+ {errors.selectedCemeteryId}
+
+ )}
+
+
+
+
+
+ {/* CTAs */}
+
+ {onSaveAndExit ? (
+
+ ) : (
+
+ )}
+
+
+
+
+ );
+};
+
+CemeteryStep.displayName = 'CemeteryStep';
+export default CemeteryStep;
diff --git a/src/components/pages/CemeteryStep/index.ts b/src/components/pages/CemeteryStep/index.ts
new file mode 100644
index 0000000..279b9e4
--- /dev/null
+++ b/src/components/pages/CemeteryStep/index.ts
@@ -0,0 +1,7 @@
+export { CemeteryStep, default } from './CemeteryStep';
+export type {
+ CemeteryStepProps,
+ CemeteryStepValues,
+ CemeteryStepErrors,
+ Cemetery,
+} from './CemeteryStep';