diff --git a/app/components/submission/ReviewerSuggestions/FormSections.js b/app/components/submission/ReviewerSuggestions/FormSections.js
new file mode 100644
index 0000000000000000000000000000000000000000..a3fb1776df14d28c48278520e772649f170b9d2c
--- /dev/null
+++ b/app/components/submission/ReviewerSuggestions/FormSections.js
@@ -0,0 +1,128 @@
+import React from 'react'
+import { Flex, Box } from 'grid-styled'
+import { Checkbox } from '@pubsweet/ui'
+
+import ValidatedField from '../../ui/atoms/ValidatedField'
+import CalloutBox from '../../ui/atoms/CalloutBox'
+import Textarea from '../../ui/atoms/Textarea'
+
+export const SuggestedSeniorEditorRow = ({ rowIndex }) => (
+  <Flex>
+    <Box width={1 / 2}>
+      <ValidatedField
+        label="Suggested senior editor"
+        name={`suggestedSeniorEditors.${rowIndex}`}
+      />
+    </Box>
+    <Box width={1 / 2}>
+      <ValidatedField
+        label="Suggested senior editor"
+        name={`suggestedSeniorEditors.${rowIndex + 1}`}
+      />
+    </Box>
+  </Flex>
+)
+
+export const ExcludedSeniorEditor = ({ index }) => (
+  <CalloutBox>
+    <ValidatedField
+      label="Excluded senior editor"
+      name={`opposedSeniorEditors.${index}.name`}
+    />
+    <ValidatedField
+      component={Textarea}
+      label="Reason for exclusion"
+      name={`opposedSeniorEditors.${index}.reason`}
+    />
+  </CalloutBox>
+)
+
+export const SuggestedReviewingEditorRow = ({ rowIndex }) => (
+  <Flex>
+    <Box width={1 / 2}>
+      <ValidatedField
+        label="Suggested reviewing editor"
+        name={`suggestedReviewingEditors.${rowIndex}`}
+      />
+    </Box>
+    <Box width={1 / 2}>
+      <ValidatedField
+        label="Suggested reviewing editor"
+        name={`suggestedReviewingEditors.${rowIndex + 1}`}
+      />
+    </Box>
+  </Flex>
+)
+
+export const ExcludedReviewingEditor = ({ index }) => (
+  <CalloutBox>
+    <ValidatedField
+      label="Excluded reviewing editor"
+      name={`opposedReviewingEditors.${index}.name`}
+    />
+    <ValidatedField
+      component={Textarea}
+      label="Reason for exclusion"
+      name={`opposedSeniorEditors.${index}.reason`}
+    />
+  </CalloutBox>
+)
+
+export const SuggestedReviewer = ({ index }) => (
+  <Flex>
+    <Box width={1 / 2}>
+      <ValidatedField
+        label="Suggested reviewer name"
+        name={`suggestedReviewers.${index}.name`}
+      />
+    </Box>
+    <Box width={1 / 2}>
+      <ValidatedField
+        label="Suggested reviewer email"
+        name={`suggestedReviewers.${index}.email`}
+        type="email"
+      />
+    </Box>
+  </Flex>
+)
+export const ExcludedReviewer = ({ index }) => (
+  <CalloutBox>
+    <Flex>
+      <Box width={1 / 2}>
+        <ValidatedField
+          label="Excluded reviewer name"
+          name={`opposedReviewers.${index}.name`}
+        />
+      </Box>
+      <Box width={1 / 2}>
+        <ValidatedField
+          label="Excluded reviewer email"
+          name={`opposedReviewers.${index}.email`}
+          type="email"
+        />
+      </Box>
+    </Flex>
+    <ValidatedField
+      component={Textarea}
+      label="Reason for exclusion"
+      name={`opposedReviewers.${index}.reason`}
+    />
+  </CalloutBox>
+)
+
+// pass `value` prop to `checked`
+const ValueCheckbox = ({ value, validationStatus, ...props }) => (
+  <Box mb={3}>
+    <Checkbox checked={value} {...props} />
+  </Box>
+)
+
+export const Declaration = () => (
+  <Box mb={3}>
+    <ValidatedField
+      component={ValueCheckbox}
+      label="I declare that, to the best of my knowledge, these experts have no conflict of interest"
+      name="declaration"
+    />
+  </Box>
+)
diff --git a/app/components/submission/ReviewerSuggestions/ReviewerSuggestions.js b/app/components/submission/ReviewerSuggestions/ReviewerSuggestions.js
index e2e9cfaff4b8b9e409506c7a5ef6101ea74630ee..6685a3eb5f9f0988882effca8b0de9dae1675ec2 100644
--- a/app/components/submission/ReviewerSuggestions/ReviewerSuggestions.js
+++ b/app/components/submission/ReviewerSuggestions/ReviewerSuggestions.js
@@ -1,50 +1,136 @@
 import React from 'react'
-import { Flex, Box } from 'grid-styled'
-import { Button, H1 } from '@pubsweet/ui'
+import { Box } from 'grid-styled'
+import { Button, H1, PlainButton } from '@pubsweet/ui'
 
-import ValidatedField from '../../ui/atoms/ValidatedField'
 import ButtonLink from '../../ui/atoms/ButtonLink'
 import ProgressBar from '../ProgressBar'
+import {
+  Declaration,
+  ExcludedReviewer,
+  ExcludedReviewingEditor,
+  ExcludedSeniorEditor,
+  SuggestedReviewer,
+  SuggestedReviewingEditorRow,
+  SuggestedSeniorEditorRow,
+} from './FormSections'
 
-const ReviewerSuggestions = ({ handleSubmit }) => (
+const MoreButton = ({
+  empty,
+  fieldName,
+  roleName,
+  setFieldValue,
+  type = 'suggest',
+  more = 'another',
+  values,
+}) => (
+  <PlainButton
+    onClick={() =>
+      setFieldValue(fieldName, values[fieldName].concat(empty), false)
+    }
+    type="button"
+  >
+    {type} {values[fieldName].length ? more : 'a'} {roleName}
+  </PlainButton>
+)
+
+const MAX_EXCLUDED_EDITORS = 2
+
+const ReviewerSuggestions = ({
+  handleSubmit,
+  values,
+  setValues,
+  setFieldValue,
+}) => (
   <form noValidate onSubmit={handleSubmit}>
     <ProgressBar currentStep={3} />
 
     <H1>Who would you like to review your work?</H1>
 
-    <ValidatedField label="Suggested editor(s)" name="suggestedEditors" />
-    <ValidatedField label="Excluded editor(s)" name="excludedEditors" />
-    <Flex>
-      <Box width={1 / 2}>
-        <ValidatedField
-          label="Suggested reviewer name"
-          name="suggestedReviewerName"
-        />
-      </Box>
-      <Box width={1 / 2}>
-        <ValidatedField
-          label="Suggested reviewer email"
-          name="suggestedReviewerEmail"
-          type="email"
-        />
-      </Box>
-    </Flex>
-
-    <Flex>
-      <Box width={1 / 2}>
-        <ValidatedField
-          label="Excluded reviewer name"
-          name="excludedReviewerName"
-        />
-      </Box>
-      <Box width={1 / 2}>
-        <ValidatedField
-          label="Excluded reviewer email"
-          name="exludedReviewerEmail"
-          type="email"
-        />
+    <SuggestedSeniorEditorRow rowIndex={0} />
+
+    {values.opposedSeniorEditors.map((_, index) => (
+      // eslint-disable-next-line react/no-array-index-key
+      <ExcludedSeniorEditor index={index} key={index} />
+    ))}
+
+    {values.opposedSeniorEditors.length < MAX_EXCLUDED_EDITORS && (
+      <Box my={3}>
+        Would you like to{' '}
+        <MoreButton
+          empty={{ name: '', reason: '' }}
+          fieldName="opposedSeniorEditors"
+          roleName="senior editor"
+          setFieldValue={setFieldValue}
+          type="exclude"
+          values={values}
+        />?
       </Box>
-    </Flex>
+    )}
+
+    {values.suggestedReviewingEditors
+      .filter((_, index) => index % 2)
+      .map((_, rowIndex) => (
+        // eslint-disable-next-line react/no-array-index-key
+        <SuggestedReviewingEditorRow key={rowIndex} rowIndex={rowIndex} />
+      ))}
+
+    {values.opposedReviewingEditors.map((_, index) => (
+      // eslint-disable-next-line react/no-array-index-key
+      <ExcludedReviewingEditor index={index} key={index} />
+    ))}
+
+    <Box my={3}>
+      Would you like to{' '}
+      <MoreButton
+        empty={['', '']}
+        fieldName="suggestedReviewingEditors"
+        more="more"
+        roleName="reviewing editors"
+        setFieldValue={setFieldValue}
+        values={values}
+      />{' '}
+      or{' '}
+      <MoreButton
+        empty={{ name: '', reason: '' }}
+        fieldName="opposedReviewingEditors"
+        roleName="reviewing editor"
+        setFieldValue={setFieldValue}
+        type="exclude"
+        values={values}
+      />?
+    </Box>
+
+    {values.suggestedReviewers.map((_, index) => (
+      // eslint-disable-next-line react/no-array-index-key
+      <SuggestedReviewer index={index} key={index} />
+    ))}
+
+    {values.opposedReviewers.map((_, index) => (
+      // eslint-disable-next-line react/no-array-index-key
+      <ExcludedReviewer index={index} key={index} />
+    ))}
+
+    <Box my={3}>
+      Would you like to{' '}
+      <MoreButton
+        empty=""
+        fieldName="suggestedReviewers"
+        roleName="reviewer"
+        setFieldValue={setFieldValue}
+        values={values}
+      />{' '}
+      or{' '}
+      <MoreButton
+        empty={{ name: '', email: '', reason: '' }}
+        fieldName="opposedReviewers"
+        roleName="reviewer"
+        setFieldValue={setFieldValue}
+        type="exclude"
+        values={values}
+      />?
+    </Box>
+
+    <Declaration />
 
     <Button primary type="submit">
       Next
diff --git a/app/components/submission/ReviewerSuggestions/ReviewerSuggestions.test.js b/app/components/submission/ReviewerSuggestions/ReviewerSuggestions.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..7aadc881d15c3bc2b3deb1ea9d5884ca84a43eef
--- /dev/null
+++ b/app/components/submission/ReviewerSuggestions/ReviewerSuggestions.test.js
@@ -0,0 +1,46 @@
+import React from 'react'
+import { mount } from 'enzyme'
+import { Formik } from 'formik'
+import { MemoryRouter } from 'react-router-dom'
+
+import ReviewerSuggestions from './ReviewerSuggestions'
+import { empty, schema } from './ReviewerSuggestionsSchema'
+
+function makeWrapper(props) {
+  return mount(
+    <MemoryRouter>
+      <Formik
+        component={ReviewerSuggestions}
+        initialValues={empty}
+        onSubmit={jest.fn()}
+        validationSchema={schema}
+        {...props}
+      />
+    </MemoryRouter>,
+  )
+}
+
+function countInputsMatching(wrapper, regex) {
+  return wrapper
+    .find('input')
+    .filterWhere(node => node.prop('name').match(regex)).length
+}
+
+describe('ReviewerSuggestions component', () => {
+  it('renders default form', () => {
+    const wrapper = makeWrapper()
+    expect(countInputsMatching(wrapper, /suggestedSeniorEditor/)).toBe(2)
+    expect(countInputsMatching(wrapper, /suggestedReviewingEditor/)).toBe(2)
+    expect(countInputsMatching(wrapper, /suggestedReviewer.+name/)).toBe(3)
+  })
+
+  it('adds excluded senior editor', () => {
+    const wrapper = makeWrapper()
+    expect(countInputsMatching(wrapper, /opposedSeniorEditor/)).toBe(0)
+    wrapper
+      .find('MoreButton')
+      .at(0)
+      .simulate('click')
+    expect(countInputsMatching(wrapper, /opposedSeniorEditor/)).toBe(1)
+  })
+})
diff --git a/app/components/submission/ReviewerSuggestions/ReviewerSuggestionsSchema.js b/app/components/submission/ReviewerSuggestions/ReviewerSuggestionsSchema.js
index 8c69f8bebc1188e908a95087bbf27c4308f94c17..b0556ad60707471fe199039614cabdc68cf097e0 100644
--- a/app/components/submission/ReviewerSuggestions/ReviewerSuggestionsSchema.js
+++ b/app/components/submission/ReviewerSuggestions/ReviewerSuggestionsSchema.js
@@ -1,19 +1,70 @@
 import yup from 'yup'
 
-const email = () => yup.string().email('Must be a valid email address')
+// TODO only the initially displayed fields should be required,
+// fields added by the user should be optional
+
+const suggestedEditorValidator = () =>
+  yup.array(yup.string().required('Required'))
+
+const opposedEditorValidator = () =>
+  yup.array(
+    yup.object({
+      name: yup.string().required('Required'),
+      reason: yup.string().required('Required'),
+    }),
+  )
+
+const suggestedReviewerValidator = () =>
+  yup.array(
+    yup.object({
+      name: yup.string().required('Name is required'),
+      email: yup
+        .string()
+        .email('Must be a valid email')
+        .required('Email is required'),
+    }),
+  )
+
+const opposedReviewerValidator = () =>
+  yup.array(
+    yup.object({
+      name: yup.string().required('Name is required'),
+      email: yup
+        .string()
+        .email('Must be a valid email')
+        .required('Email is required'),
+      reason: yup.string().required('Required'),
+    }),
+  )
 
 const schema = yup.object().shape({
-  suggestedReviewerEmail: email(),
-  excludedReviewerEmail: email(),
+  suggestedSeniorEditors: suggestedEditorValidator(),
+  opposedSeniorEditors: opposedEditorValidator(),
+  suggestedReviewingEditors: suggestedEditorValidator(),
+  opposedReviewingEditors: opposedEditorValidator(),
+  suggestedReviewers: suggestedReviewerValidator(),
+  opposedReviewers: opposedReviewerValidator(),
+  declaration: yup
+    .bool()
+    .required()
+    .oneOf(
+      [true],
+      'Please do not suggest people with a known confilct of interest',
+    ),
 })
 
 const empty = {
-  suggestedEditors: '',
-  excludedEditors: '',
-  suggestedReviewerName: '',
-  excludedReviewerName: '',
-  suggestedReviewerEmail: '',
-  excludedReviewerEmail: '',
+  suggestedSeniorEditors: ['', ''],
+  opposedSeniorEditors: [],
+  suggestedReviewingEditors: ['', ''],
+  opposedReviewingEditors: [],
+  suggestedReviewers: [
+    { name: '', email: '' },
+    { name: '', email: '' },
+    { name: '', email: '' },
+  ],
+  opposedReviewers: [],
+  declaration: false,
 }
 
 export { schema, empty }
diff --git a/app/components/ui/atoms/CalloutBox.js b/app/components/ui/atoms/CalloutBox.js
new file mode 100644
index 0000000000000000000000000000000000000000..00b36f13c455a6481d4b9a9c986ce7fd5e09510c
--- /dev/null
+++ b/app/components/ui/atoms/CalloutBox.js
@@ -0,0 +1,10 @@
+import styled from 'styled-components'
+import { Box } from 'grid-styled'
+import { th } from '@pubsweet/ui'
+
+const CalloutBox = styled(Box).attrs({ mx: -3, px: 3, mb: 3 })`
+  border: ${th('borderWidth')} ${th('borderStyle')} ${th('borderColor')};
+  border-radius: ${th('borderRadius')};
+`
+
+export default CalloutBox
diff --git a/app/components/ui/atoms/Textarea.js b/app/components/ui/atoms/Textarea.js
new file mode 100644
index 0000000000000000000000000000000000000000..4a7f8035f3d78f8fad46bbd362a960ad81445416
--- /dev/null
+++ b/app/components/ui/atoms/Textarea.js
@@ -0,0 +1,58 @@
+import React from 'react'
+import styled from 'styled-components'
+import { th } from '@pubsweet/ui'
+
+const Root = styled.div`
+  display: flex;
+  flex-direction: column;
+  max-width: calc(${th('gridUnit')} * 14);
+  margin-bottom: ${th('gridUnit')};
+`
+
+const Label = styled.label`
+  font-size: ${th('fontSizeBaseSmall')};
+  display: block;
+`
+
+const borderColor = ({ theme, validationStatus = 'default' }) =>
+  ({
+    error: theme.colorError,
+    success: theme.colorSuccess,
+    warning: theme.colorWarning,
+    default: theme.colorBorder,
+  }[validationStatus])
+
+const Input = styled.textarea`
+  border: ${th('borderWidth')} ${th('borderStyle')} ${borderColor};
+
+  border-radius: ${th('borderRadius')};
+
+  font-family: inherit;
+  font-size: inherit;
+
+  padding: calc(${th('gridUnit')} / 2);
+  min-height: calc(${th('fontLineHeight')} * 2);
+
+  &::placeholder {
+    color: ${th('colorTextPlaceholder')};
+  }
+`
+
+class Textarea extends React.Component {
+  componentWillMount() {
+    // generate a unique ID to link the label to the input
+    // note this may not play well with server rendering
+    this.inputId = `textarea-${Math.round(Math.random() * 1e12).toString(36)}`
+  }
+  render() {
+    const { label, value = '', readonly, ...props } = this.props
+    return (
+      <Root>
+        {label && <Label htmlFor={this.inputId}>{label}</Label>}
+        <Input id={this.inputId} readOnly={readonly} value={value} {...props} />
+      </Root>
+    )
+  }
+}
+
+export default Textarea
diff --git a/app/components/ui/atoms/ValidatedField.js b/app/components/ui/atoms/ValidatedField.js
index a1180c48f7105506f96f5f71f17e075394db9ce6..797c6cc9d2bdced16f4d09c92228da7422e41fe8 100644
--- a/app/components/ui/atoms/ValidatedField.js
+++ b/app/components/ui/atoms/ValidatedField.js
@@ -15,7 +15,11 @@ const ErrorMessage = styled.div`
   color: ${th('colorError')};
 `
 
-export default ({ name, component: FieldComponent = TextField, ...props }) => {
+const ValidatedField = ({
+  name,
+  component: FieldComponent = TextField,
+  ...props
+}) => {
   const render = ({ field, form }) => {
     const touched = get(form.touched, name)
     const errors = get(form.errors, name)
@@ -42,3 +46,5 @@ export default ({ name, component: FieldComponent = TextField, ...props }) => {
 
   return <Field name={name} render={render} />
 }
+
+export default ValidatedField