diff --git a/docs/assets/pubsweet-logo.svg b/docs/assets/pubsweet-logo.svg
index 88f361552dfa2c7b9f398651768b1dd19c3069ca..2ed2fd0b8bdce89ab118c15500a5e3164bd984b9 100644
--- a/docs/assets/pubsweet-logo.svg
+++ b/docs/assets/pubsweet-logo.svg
@@ -1,4 +1,4 @@
-<svg width="762" height="763" viewBox="0 0 762 763" fill="none" xmlns="http://www.w3.org/2000/svg">
+<svg viewBox="0 0 762 763" fill="none" xmlns="http://www.w3.org/2000/svg">
 <mask id="mask0" mask-type="alpha" maskUnits="userSpaceOnUse" x="380" y="190" width="382" height="573">
 <path d="M241.963 50.406L329.077 1.66893e-08C394.807 113.976 399.688 259.021 329.294 381.218C258.941 503.34 131.271 571.618 -2.6226e-08 571.634V470.702L242.484 330.705V50.707L241.963 50.406Z" transform="translate(380.802 190.503)" fill="white"/>
 </mask>
diff --git a/docs/styleguide.config.js b/docs/styleguide.config.js
index adaaa53134117a62d3fbe74e0cef88e44aa549e3..87a73106a95281604188c90a8f59f90349b12283 100644
--- a/docs/styleguide.config.js
+++ b/docs/styleguide.config.js
@@ -134,7 +134,6 @@ module.exports = {
     '**/components/Manage/**',
     '**/components/**/*.config.js',
     '**/components/*-server/**',
-    '**/components/MediumDraft/CustomImageSideButton.jsx',
     '**/node_modules/**',
     '**/*.test.{js,jsx}',
   ],
diff --git a/packages/components/Signup/Signup.md b/packages/components/Signup/Signup.md
new file mode 100644
index 0000000000000000000000000000000000000000..91cd341d7cc97a55b5bc85bb56147bea04240a85
--- /dev/null
+++ b/packages/components/Signup/Signup.md
@@ -0,0 +1,39 @@
+A signup form
+
+```js
+const { withFormik } = require('formik')
+
+const SignupForm = withFormik({
+  initialValues: {
+    username: '',
+    password: '',
+  },
+  mapPropsToValues: props => ({
+    username: props.username,
+    password: props.password,
+  }),
+  displayName: 'Signup',
+  handleSubmit: val => console.log(val),
+})(Signup)
+;<SignupForm />
+```
+
+Can also have a logo:
+
+```js
+const { withFormik } = require('formik')
+
+const SignupForm = withFormik({
+  initialValues: {
+    username: '',
+    password: '',
+  },
+  mapPropsToValues: props => ({
+    username: props.username,
+    password: props.password,
+  }),
+  displayName: 'Signup',
+  handleSubmit: val => console.log(val),
+})(Signup)
+;<SignupForm logo="pubsweet-logo.svg" />
+```
diff --git a/packages/components/Signup/index.js b/packages/components/Signup/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..ce5d7de642c988287390b34b9c4cb4540b9a0306
--- /dev/null
+++ b/packages/components/Signup/index.js
@@ -0,0 +1,3 @@
+import Signup from './src/SignupContainer'
+
+export default Signup
diff --git a/packages/components/Signup/package.json b/packages/components/Signup/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..00ea31c6c7913812bfa23d049f20175100e80a3b
--- /dev/null
+++ b/packages/components/Signup/package.json
@@ -0,0 +1,28 @@
+{
+  "name": "editoria-component-signup",
+  "version": "1.0.41",
+  "description": "Basic signup form component for PubSweet",
+  "main": "index.js",
+  "author": "Collaborative Knowledge Foundation",
+  "license": "MIT",
+  "dependencies": {
+    "@pubsweet/ui": "^9.1.3",
+    "@pubsweet/ui-toolkit": "2.0.7",
+    "formik": "^1.3.0",
+    "prop-types": "^15.5.10", 
+    "recompose": "^0.30.0"
+  },
+  "peerDependencies": {
+    "graphql-tag": "^2.10.0",
+    "pubsweet-client": ">=1.0.0",
+    "react": ">=16",
+    "react-apollo": "^2.3.3",
+    "react-router": "^4.3.1",
+    "styled-components": "^4.1.3"
+  },
+  "repository": {
+    "type": "git",
+    "url": "https://gitlab.coko.foundation/pubsweet/pubsweet",
+    "path": "Signup"
+  }
+}
diff --git a/packages/components/Signup/src/Signup.jsx b/packages/components/Signup/src/Signup.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..732cec4a7885d08ba24a5780736d4b2bcb4870f7
--- /dev/null
+++ b/packages/components/Signup/src/Signup.jsx
@@ -0,0 +1,72 @@
+import React from 'react'
+import { Field } from 'formik'
+import { override } from '@pubsweet/ui-toolkit'
+import styled from 'styled-components'
+
+import {
+  CenteredColumn,
+  Link,
+  H1,
+  ErrorText,
+  Button,
+  TextField,
+} from '@pubsweet/ui'
+
+const FormContainer = styled.div`
+  ${override('Login.FormContainer')};
+`
+
+const Logo = styled.div`
+  width: 100%;
+  max-width: 100px;
+  margin: 0 auto;
+  ${override('Login.Logo')};
+`
+Logo.displayName = 'Logo'
+
+const UsernameInput = props => (
+  <TextField label="Username" {...props.field} placeholder="Username" />
+)
+
+const EmailInput = props => (
+  <TextField label="Email" {...props.field} placeholder="Email" type="email" />
+)
+const PasswordInput = props => (
+  <TextField
+    label="Password"
+    {...props.field}
+    placeholder="Password"
+    type="password"
+  />
+)
+
+const Signup = ({ error, handleSubmit, logo = null }) => (
+  <CenteredColumn small>
+    {logo && (
+      <Logo>
+        <img alt="pubsweet-logo" src={`${logo}`} />
+      </Logo>
+    )}
+    <FormContainer>
+      <H1>Sign up</H1>
+
+      {error && <ErrorText>{error}</ErrorText>}
+
+      <form onSubmit={handleSubmit}>
+        <Field component={UsernameInput} name="username" />
+        <Field component={EmailInput} name="email" />
+        <Field component={PasswordInput} name="password" />
+        <Button primary type="submit">
+          Sign up
+        </Button>
+      </form>
+
+      <div>
+        <span>Already have an account? </span>
+        <Link to="/login">Login</Link>
+      </div>
+    </FormContainer>
+  </CenteredColumn>
+)
+
+export default Signup
diff --git a/packages/components/Signup/src/SignupContainer.js b/packages/components/Signup/src/SignupContainer.js
new file mode 100644
index 0000000000000000000000000000000000000000..36bf4d98e1d6d9a9677261aef2419f756fa6d81b
--- /dev/null
+++ b/packages/components/Signup/src/SignupContainer.js
@@ -0,0 +1,32 @@
+import { compose } from 'recompose'
+import { withFormik } from 'formik'
+import { graphql } from 'react-apollo'
+import { SIGNUP_USER } from './graphql/mutations'
+
+import Signup from './Signup'
+
+const handleSubmit = (values, { props, setSubmitting, setErrors }) =>
+  props.signupUser({
+    variables: { input: values },
+  })
+
+const enhancedFormik = withFormik({
+  initialValues: {
+    username: '',
+    email: '',
+    password: '',
+  },
+  mapPropsToValues: props => ({
+    username: props.username,
+    password: props.password,
+    email: props.email,
+  }),
+  displayName: 'signup',
+  handleSubmit,
+})(Signup)
+
+export default compose(
+  graphql(SIGNUP_USER, {
+    name: 'signupUser',
+  }),
+)(enhancedFormik)
diff --git a/packages/components/Signup/src/graphql/mutations/index.js b/packages/components/Signup/src/graphql/mutations/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..49150319715409f25dfbb390757159ef33523eb8
--- /dev/null
+++ b/packages/components/Signup/src/graphql/mutations/index.js
@@ -0,0 +1,12 @@
+import gql from 'graphql-tag'
+
+export const SIGNUP_USER = gql`
+  mutation($input: UserInput) {
+    createUser(input: $input) {
+      id
+      type
+      username
+      email
+    }
+  }
+`
diff --git a/packages/components/Signup/test/Signup.test.jsx b/packages/components/Signup/test/Signup.test.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..c58f45b04822b5d76f9a0c809248a7e52fde856b
--- /dev/null
+++ b/packages/components/Signup/test/Signup.test.jsx
@@ -0,0 +1,38 @@
+import { shallow } from 'enzyme'
+import React from 'react'
+import { Field } from 'formik'
+import { Button, ErrorText } from '@pubsweet/ui'
+
+import Signup from '../src/Signup'
+
+describe('<Signup />', () => {
+  const makeWrapper = (props = {}) => shallow(<Signup {...props} />)
+
+  it('renders the login form', () => {
+    const wrapper = makeWrapper()
+    expect(wrapper.debug()).toMatchSnapshot()
+    expect(wrapper.find(Field)).toHaveLength(3)
+    expect(wrapper.find(Button)).toHaveLength(1)
+    expect(wrapper.find({ to: '/login' })).toHaveLength(1)
+  })
+
+  it('shows error', () => {
+    const wrapper = makeWrapper({ error: 'Yikes!' })
+    expect(wrapper.find(ErrorText)).toHaveLength(1)
+  })
+
+  it('can hide logo', () => {
+    const logo = 'data:image/gif;base64,R0lGODlhDwAPAKECAAAAzMzM/////'
+    const wrapper1 = makeWrapper({ logo })
+    const wrapper2 = makeWrapper()
+    expect(wrapper1.find('Logo')).toHaveLength(1)
+    expect(wrapper2.find('Logo')).toHaveLength(0)
+  })
+
+  it('triggers submit handler', () => {
+    const handleSubmit = jest.fn()
+    const wrapper = makeWrapper({ handleSubmit })
+    wrapper.find('form').simulate('submit')
+    expect(handleSubmit).toHaveBeenCalled()
+  })
+})
diff --git a/packages/components/Signup/test/__snapshots__/Signup.test.jsx.snap b/packages/components/Signup/test/__snapshots__/Signup.test.jsx.snap
new file mode 100644
index 0000000000000000000000000000000000000000..7ebce37b08af00279d7cf785eafb5a30c654cdc3
--- /dev/null
+++ b/packages/components/Signup/test/__snapshots__/Signup.test.jsx.snap
@@ -0,0 +1,27 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`<Signup /> renders the login form 1`] = `
+"<styled.div small={true}>
+  <styled.div>
+    <styled.h1>
+      Sign up
+    </styled.h1>
+    <form onSubmit={[undefined]}>
+      <FormikConnect(FieldInner) component={[Function: UsernameInput]} name=\\"username\\" />
+      <FormikConnect(FieldInner) component={[Function: EmailInput]} name=\\"email\\" />
+      <FormikConnect(FieldInner) component={[Function: PasswordInput]} name=\\"password\\" />
+      <styled.button primary={true} type=\\"submit\\">
+        Sign up
+      </styled.button>
+    </form>
+    <div>
+      <span>
+        Already have an account? 
+      </span>
+      <Styled(Link) to=\\"/login\\">
+        Login
+      </Styled(Link)>
+    </div>
+  </styled.div>
+</styled.div>"
+`;
diff --git a/yarn.lock b/yarn.lock
index 3bbf5d4dda732dbf3230a488ca5dc7d6efb60db9..c2266698c592b7c4b8c0ea294f701ad8d80dbecf 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5637,11 +5637,6 @@ es-to-primitive@^1.1.1, es-to-primitive@^1.2.0:
     is-date-object "^1.0.1"
     is-symbol "^1.0.2"
 
-es6-error@^4.1.1:
-  version "4.1.1"
-  resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d"
-  integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==
-
 es6-object-assign@~1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/es6-object-assign/-/es6-object-assign-1.1.0.tgz#c2c3582656247c39ea107cb1e6652b6f9f24523c"
@@ -7645,7 +7640,7 @@ hoist-non-react-statics@^2.1.0, hoist-non-react-statics@^2.3.1:
   resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.0.tgz#d2ca2dfc19c5a91c5a6615ce8e564ef0347e2a40"
   integrity sha512-6Bl6XsDT1ntE0lHbIhr4Kp2PGcleGZ66qu5Jqk8lc0Xc/IeG6gVLmwUGs/K0Us+L8VWoKgj0uWdPMataOsm31w==
 
-hoist-non-react-statics@^2.5.0, hoist-non-react-statics@^2.5.4, hoist-non-react-statics@^2.5.5:
+hoist-non-react-statics@^2.5.0, hoist-non-react-statics@^2.5.5:
   version "2.5.5"
   resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47"
   integrity sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==
@@ -9864,7 +9859,15 @@ locate-path@^3.0.0:
     p-locate "^3.0.0"
     path-exists "^3.0.0"
 
-lodash-es@^4.17.10, lodash-es@^4.17.11:
+lock-verify@^2.0.2:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/lock-verify/-/lock-verify-2.0.2.tgz#148e4f85974915c9e3c34d694b7de9ecb18ee7a8"
+  integrity sha512-QNVwK0EGZBS4R3YQ7F1Ox8p41Po9VGl2QG/2GsuvTbkJZYSsPeWHKMbbH6iZMCHWSMww5nrJroZYnGzI4cePuw==
+  dependencies:
+    npm-package-arg "^5.1.2 || 6"
+    semver "^5.4.1"
+
+lodash-es@^4.17.11:
   version "4.17.11"
   resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.11.tgz#145ab4a7ac5c5e52a3531fb4f310255a152b4be0"
   integrity sha512-DHb1ub+rMjjrxqlB3H56/6MXtm1lSksDp2rA2cNWjG8mlDUYFhUj3Di2Zn5IwSU87xLv8tNIQ7sSwE/YOX/D/Q==
@@ -13940,33 +13943,7 @@ reduce-function-call@^1.0.1:
   dependencies:
     balanced-match "^0.4.2"
 
-redux-form@^7.0.3:
-  version "7.4.2"
-  resolved "https://registry.yarnpkg.com/redux-form/-/redux-form-7.4.2.tgz#d6061088fb682eb9fc5fb9749bd8b102f03154b0"
-  integrity sha512-QxC36s4Lelx5Cr8dbpxqvl23dwYOydeAX8c6YPmgkz/Dhj053C16S2qoyZN6LO6HJ2oUF00rKsAyE94GwOUhFA==
-  dependencies:
-    es6-error "^4.1.1"
-    hoist-non-react-statics "^2.5.4"
-    invariant "^2.2.4"
-    is-promise "^2.1.0"
-    lodash "^4.17.10"
-    lodash-es "^4.17.10"
-    prop-types "^15.6.1"
-    react-lifecycles-compat "^3.0.4"
-
-redux-logger@^3.0.1:
-  version "3.0.6"
-  resolved "https://registry.yarnpkg.com/redux-logger/-/redux-logger-3.0.6.tgz#f7555966f3098f3c88604c449cf0baf5778274bf"
-  integrity sha1-91VZZvMJjzyIYExEnPC69XeCdL8=
-  dependencies:
-    deep-diff "^0.3.5"
-
-redux-thunk@^2.2.0:
-  version "2.3.0"
-  resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622"
-  integrity sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw==
-
-redux@^3.6.0, redux@^3.7.1, redux@^3.7.2:
+redux@^3.7.1, redux@^3.7.2:
   version "3.7.2"
   resolved "https://registry.yarnpkg.com/redux/-/redux-3.7.2.tgz#06b73123215901d25d065be342eb026bc1c8537b"
   integrity sha512-pNqnf9q1hI5HHZRBkj3bAngGZW/JMCmexDlOxw4XagXY2o1327nHH54LoTjiPJ0gizoqPDRqWyX/00g0hD6w+A==