diff --git a/frontend/.eslintrc.json b/frontend/.eslintrc.json
index ef74cbdbf973cfbfe0a0c596ae4e97961d923f76..0557dd77520ee8597aa6213c09a952ee8e5499d4 100644
--- a/frontend/.eslintrc.json
+++ b/frontend/.eslintrc.json
@@ -22,6 +22,7 @@
   "plugins": ["react", "@typescript-eslint"],
   "rules": {
     "semi": "off",
+    "react/jsx-props-no-spreading": "off",
     "@typescript-eslint/semi": ["error", "never"]
   }
 }
diff --git a/frontend/public/index.html b/frontend/public/index.html
index aa069f27cbd9d53394428171c3989fd03db73c76..2bc0e084ce0bca917aa25836d941d54011b64d6a 100644
--- a/frontend/public/index.html
+++ b/frontend/public/index.html
@@ -10,34 +10,11 @@
       content="Web site created using create-react-app"
     />
     <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
-    <!--
-      manifest.json provides metadata used when your web app is installed on a
-      user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-    -->
     <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
-    <!--
-      Notice the use of %PUBLIC_URL% in the tags above.
-      It will be replaced with the URL of the `public` folder during the build.
-      Only files inside the `public` folder can be referenced from the HTML.
-
-      Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
-      work correctly both with client-side routing and a non-root public URL.
-      Learn how to configure a non-root public URL by running `npm run build`.
-    -->
-    <title>React App</title>
+    <title>Guest Registration</title>
   </head>
   <body>
     <noscript>You need to enable JavaScript to run this app.</noscript>
     <div id="root"></div>
-    <!--
-      This HTML file is a template.
-      If you open it directly in the browser, you will see an empty page.
-
-      You can add webfonts, meta tags, or analytics to this file.
-      The build step will place the bundled scripts into the <body> tag.
-
-      To begin the development, run `npm start` or `yarn start`.
-      To create a production bundle, use `npm run build` or `yarn build`.
-    -->
   </body>
 </html>
diff --git a/frontend/public/locales/en/common.json b/frontend/public/locales/en/common.json
index 713a15ba11df4cc1687f3576cf281ae912279930..4c2ab711ff2501d634315c3041da3394a142b2eb 100644
--- a/frontend/public/locales/en/common.json
+++ b/frontend/public/locales/en/common.json
@@ -14,5 +14,8 @@
   },
   "loading": "Loading...",
   "termsHeader": "Terms",
-  "staging": "Staging"
+  "staging": "Staging",
+  "firstName": "First name",
+  "lastName": "Last name",
+  "dateOfBirth": "Date of birth"
 }
diff --git a/frontend/public/locales/nb/common.json b/frontend/public/locales/nb/common.json
index 57d358c0dd322350f45ef0216625428a6e76b6ea..11a25642f5fb9b4214464761810703467700b845 100644
--- a/frontend/public/locales/nb/common.json
+++ b/frontend/public/locales/nb/common.json
@@ -7,12 +7,15 @@
     "change": "Bytt språk til {{lang}}"
   },
   "fnr": "Fødselsnummer",
-  "header":{
+  "header": {
     "applicationTitle": "Gjesteregistrering",
     "applicationDescription": "Registreringstjeneste for gjester",
     "selectLanguage": "Velg språk"
   },
   "loading": "Laster...",
   "termsHeader": "Vilkår",
-  "staging": "Staging"
+  "staging": "Staging",
+  "firstName": "Fornavn",
+  "lastName": "Etternavn",
+  "dateOfBirth": "Fødselsdato"
 }
diff --git a/frontend/public/locales/nn/common.json b/frontend/public/locales/nn/common.json
index 2e76d48dfcb7f5e3e9582083fdffa687b333cb54..21039b90560962d4338ef53f60348eb0d8edba48 100644
--- a/frontend/public/locales/nn/common.json
+++ b/frontend/public/locales/nn/common.json
@@ -8,12 +8,15 @@
     "change": "Bytt språk til {{lang}}"
   },
   "fnr": "National identity number",
-  "header":{
+  "header": {
     "applicationTitle": "Gjesteregistrering",
     "applicationDescription": "Registreringsteneste for gjester",
     "selectLanguage": "Velg språk"
   },
   "loading": "Lastar...",
   "termsHeader": "Vilkår",
-  "staging": "Staging"
+  "staging": "Staging",
+  "firstName": "Fornamn",
+  "lastName": "Etternamn",
+  "dateOfBirth": "Fødselsdato"
 }
diff --git a/frontend/src/components/button/index.tsx b/frontend/src/components/button/index.tsx
index 9eb200519a6b51d5342bd15a8a5b9a1b188e2fe7..7054c37015069fd236de9a1312f0979977135538 100644
--- a/frontend/src/components/button/index.tsx
+++ b/frontend/src/components/button/index.tsx
@@ -2,6 +2,7 @@ import styled from 'styled-components/macro'
 
 export const Button = styled.a`
   display: inline-block;
+  cursor: pointer;
   border-radius: 3px;
   padding: 0.5rem 0;
   margin: 0.5rem 1rem;
diff --git a/frontend/src/components/dateinput/index.tsx b/frontend/src/components/dateinput/index.tsx
index 215661edc9b8076bbddff1fe61a6654da2e40605..a12c37a7dd0260d5190847a53849dc3e01923f40 100644
--- a/frontend/src/components/dateinput/index.tsx
+++ b/frontend/src/components/dateinput/index.tsx
@@ -1,29 +1,25 @@
-import { addDays } from 'date-fns'
-import nb from 'date-fns/locale/nb'
-import nn from 'date-fns/locale/nn'
-import React, { useState } from 'react'
-import DatePicker, { registerLocale } from 'react-datepicker'
+import React from 'react'
+import DatePicker, {
+  ReactDatePickerProps,
+  registerLocale,
+} from 'react-datepicker'
+
 import 'react-datepicker/dist/react-datepicker.css'
 import { useTranslation } from 'react-i18next'
 
+import nb from 'date-fns/locale/nb'
+import nn from 'date-fns/locale/nn'
+
 registerLocale('nb', nb)
 registerLocale('nn', nn)
 // CSS Modules, react-datepicker-cssmodules.css
 // import 'react-datepicker/dist/react-datepicker-cssmodules.css';
 
-const DateInput = () => {
-  const [startDate, setStartDate] = useState(new Date())
+const DateInput = (props: ReactDatePickerProps) => {
   const { i18n } = useTranslation()
 
   return (
-    <DatePicker
-      dateFormat="yyyy-MM-dd"
-      locale={i18n.language}
-      selected={startDate}
-      minDate={startDate}
-      maxDate={addDays(new Date(), 365)}
-      onChange={(date: any) => setStartDate(date)}
-    />
+    <DatePicker dateFormat="yyyy-MM-dd" locale={i18n.language} {...props} />
   )
 }
 
diff --git a/frontend/src/components/form/fnr.tsx b/frontend/src/components/form/fnr.tsx
index 16e356125bdef88dfbae3e90d18164ef31e6b318..ee7cd1f2a920bc3b443d11f4c5a084f0321b5065 100644
--- a/frontend/src/components/form/fnr.tsx
+++ b/frontend/src/components/form/fnr.tsx
@@ -1,11 +1,7 @@
 import React from 'react'
-import validator from '@navikt/fnrvalidator'
-import { UseFormReturn } from 'react-hook-form'
 
-export function isValidIdnr(data: string) {
-  const validationResult = validator.idnr(data)
-  return validationResult.status === 'valid'
-}
+import { UseFormReturn } from 'react-hook-form'
+import { isValidFnr } from 'utils'
 
 interface FnrProps extends Partial<Pick<UseFormReturn, 'register'>> {
   name: string
@@ -26,7 +22,7 @@ function Fnr(props: FnrProps) {
         // eslint-disable-next-line react/jsx-props-no-spreading
         {...register(name, {
           required: 'Fnr is required',
-          validate: isValidIdnr,
+          validate: isValidFnr,
         })}
         id="fnr"
       />
diff --git a/frontend/src/components/form/form.tsx b/frontend/src/components/form/form.tsx
index 305eb2d302e1eb138480cff9399447c69d1c0e92..2964e7f2591152ec5e6fb2d5e83fe2ac40861538 100644
--- a/frontend/src/components/form/form.tsx
+++ b/frontend/src/components/form/form.tsx
@@ -22,6 +22,7 @@ export default function Form(props: FormProps) {
               ...{
                 // eslint-disable-next-line react/jsx-props-no-spreading
                 ...child.props,
+                control: methods.control,
                 register: methods.register,
                 errors,
                 key: child.props.name,
diff --git a/frontend/src/components/form/input.tsx b/frontend/src/components/form/input.tsx
index 231a731c4b4be4fd8e98e684373dbba1bbca134f..94e7332f465c98a0fa1561ffb3756ed4c37c8846 100644
--- a/frontend/src/components/form/input.tsx
+++ b/frontend/src/components/form/input.tsx
@@ -1,12 +1,25 @@
 import React from 'react'
+import styled from 'styled-components/macro'
 import { UseFormReturn } from 'react-hook-form'
 
-interface InputProps extends Partial<Pick<UseFormReturn, 'register'>> {
+interface InputProps
+  extends Partial<React.InputHTMLAttributes<HTMLInputElement>>,
+    Partial<UseFormReturn> {
   name: string
   errors?: any
   type?: 'text' | 'email' | 'number'
 }
 
+export const StyledInput = styled.input`
+  width: 50%;
+  border: 1px solid;
+  border-width: 0.013rem;
+  border-radius: 0.3rem;
+  border-color: #ccc;
+  box-shadow: inset 0 0 0 0.013rem #4d4d4d;
+  padding: 0.625rem;
+`
+
 function Input(props: InputProps) {
   // eslint-disable-next-line react/jsx-props-no-spreading
   const { register, name, errors, type, ...rest } = props
@@ -14,7 +27,7 @@ function Input(props: InputProps) {
     return <></>
   }
   // eslint-disable-next-line react/jsx-props-no-spreading
-  return <input {...register(name)} {...rest} />
+  return <StyledInput {...register(name)} {...rest} />
 }
 
 Input.defaultProps = {
diff --git a/frontend/src/components/page/page.tsx b/frontend/src/components/page/page.tsx
index 6a2792ecd1a150c8acb9c49016e3f2f6b7c69467..2d95a68cc813c3a214ec92d67108016c3c431d13 100644
--- a/frontend/src/components/page/page.tsx
+++ b/frontend/src/components/page/page.tsx
@@ -6,6 +6,11 @@ import styled from 'styled-components/macro'
 
 const StyledPage = styled.main`
   display: block;
+  justify-content: space-between;
+  margin: 0 auto;
+  max-width: ${(props) => props.theme.appMaxWidth};
+  padding: ${(props) =>
+    `0.5rem ${props.theme.horizontalPadding} 1rem ${props.theme.horizontalPadding}`};
 `
 
 const StyledPageHeader = styled.h2`
diff --git a/frontend/src/globalStyles.ts b/frontend/src/globalStyles.ts
index f81dda019fb53dbc61108181c833a0187aa06164..d158638476ed156b47e87db0e1e30432cd210ae0 100644
--- a/frontend/src/globalStyles.ts
+++ b/frontend/src/globalStyles.ts
@@ -1,6 +1,10 @@
 import { createGlobalStyle } from 'styled-components/macro'
 
 const GlobalStyle = createGlobalStyle`
+  html {
+    font-size: clamp(24px, calc(24px * 1vw), 36px);
+  }
+
   body {
     margin: 0;
     min-height: 100vh;
diff --git a/frontend/src/routes/register/index.tsx b/frontend/src/routes/register/index.tsx
index 42193d0c537d9d8824db10238aa5223a201cc0a8..629080bdea3a7580c42a5e41c25f53a569aa1c94 100644
--- a/frontend/src/routes/register/index.tsx
+++ b/frontend/src/routes/register/index.tsx
@@ -1,24 +1,78 @@
 import React from 'react'
 import { useTranslation } from 'react-i18next'
 
+import { StyledInput } from 'components/form/input'
 import DateInput from 'components/dateinput'
-import { Fnr, Form, Input, Select } from 'components/form'
+import Button from 'components/button'
 import Page from 'components/page'
+import { useForm, Controller, SubmitHandler } from 'react-hook-form'
+import format from 'date-fns/format'
+import { postJsonOpts } from 'utils'
+
+type RegisterFormData = {
+  first_name: string
+  last_name: string
+  date_of_birth: Date
+}
 
 export default function Register() {
-  const { t } = useTranslation(['common', 'footer'])
+  const { t } = useTranslation(['common'])
+
+  const submit: SubmitHandler<RegisterFormData> = (data) => {
+    const payload = {
+      first_name: data.first_name,
+      last_name: data.last_name,
+      date_of_birth: format(data.date_of_birth, 'yyyy-MM-dd'),
+    }
+    console.log('submitting', JSON.stringify(payload))
+    fetch('http://localhost:3000/api/ui/v1/register/', postJsonOpts(payload))
+      .then((res) => res.text())
+      .then((result) => {
+        console.log('result', result)
+      })
+      .catch((error) => {
+        console.log('error', error)
+      })
+  }
+
+  const {
+    register,
+    control,
+    handleSubmit,
+    // setValue,
+    // formState: { errors },
+  } = useForm<RegisterFormData>()
+  const onSubmit = handleSubmit(submit)
 
   return (
     <Page header="Register as a guest">
-      <Form onSubmit={() => {}}>
-        <Input name="firstName" />
-        <Input name="lastName" />
-        <DateInput />
-        <Select name="gender" options={['female', 'male', 'other']} />
-        <Fnr name={t('common:fnr')} />
-        <button type="submit">Submit</button>
-      </Form>
-      <br />
+      <form onSubmit={onSubmit}>
+        <StyledInput
+          {...register(`first_name`)}
+          placeholder={t('common:firstName')}
+        />
+        <StyledInput
+          {...register(`last_name`)}
+          placeholder={t('common:lastName')}
+        />
+        <Controller
+          name="date_of_birth"
+          control={control}
+          render={({ field }) => (
+            <DateInput
+              customInput={<StyledInput />}
+              placeholderText={t('common:dateOfBirth')}
+              onChange={(date) => field.onChange(date)}
+              selected={field.value}
+            />
+          )}
+        />
+        {/* <Select name="gender" options={['female', 'male', 'other']} />
+        <Fnr name={t('common:fnr')} /> */}
+        <Button as="button" type="submit">
+          Submit
+        </Button>
+      </form>
     </Page>
   )
 }
diff --git a/frontend/src/utils/index.ts b/frontend/src/utils/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..80e6cd182ccc39586653a9f3200ba64bcd862505
--- /dev/null
+++ b/frontend/src/utils/index.ts
@@ -0,0 +1,43 @@
+import validator from '@navikt/fnrvalidator'
+
+export function getCookie(name: string) {
+  if (!document.cookie) {
+    return null
+  }
+
+  const cookies = document.cookie
+    .split(';')
+    .map((c) => c.trim())
+    .filter((c) => c.startsWith(`${name}=`))
+
+  if (cookies.length === 0) {
+    return null
+  }
+  return decodeURIComponent(cookies[0].split('=')[1])
+}
+
+export function maybeCsrfToken() {
+  const csrfToken = getCookie('csrftoken')
+  if (!csrfToken) {
+    return null
+  }
+  return {
+    'X-CSRFToken': csrfToken,
+  }
+}
+
+export function postJsonOpts(data: object): RequestInit {
+  return {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...maybeCsrfToken(),
+    },
+    body: JSON.stringify(data),
+    credentials: 'same-origin',
+  }
+}
+
+export function isValidFnr(data: string): boolean {
+  return validator.idnr(data).status === 'valid'
+}
diff --git a/gregsite/settings/base.py b/gregsite/settings/base.py
index 758c3ed9db51bb3632d2b35bb5000a2d7b6f610f..de2c8a1be8fa12a4043e71b491b5e9e4859b8444 100644
--- a/gregsite/settings/base.py
+++ b/gregsite/settings/base.py
@@ -64,7 +64,7 @@ MIDDLEWARE = [
 REST_FRAMEWORK = {
     "DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.NamespaceVersioning",
     "DEFAULT_VERSION": "v1",
-    "ALLOWED_VERSIONS": ("v1",),
+    "ALLOWED_VERSIONS": ("v1", "gregui-v1"),
     "DEFAULT_AUTHENTICATION_CLASSES": (
         "rest_framework.authentication.TokenAuthentication",
         "rest_framework.authentication.SessionAuthentication",
diff --git a/gregsite/urls.py b/gregsite/urls.py
index e0f9859117293eb3708c6123a95cc8c0802b9e4a..649c186c85e3fcee051a11e82371e39f09ee3d13 100644
--- a/gregsite/urls.py
+++ b/gregsite/urls.py
@@ -17,10 +17,12 @@ from django.contrib import admin
 from django.urls import include, path
 
 from greg import urls as greg_urls
+from gregui import urls as gregui_urls
 
 admin.autodiscover()
 
 urlpatterns = [
     path("admin/", admin.site.urls),
     path("", include(greg_urls.urlpatterns)),
+    path("", include(gregui_urls.urlpatterns)),
 ]
diff --git a/gregui/api/__init__.py b/gregui/api/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/gregui/api/serializers/__init__.py b/gregui/api/serializers/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/gregui/api/serializers/guest.py b/gregui/api/serializers/guest.py
new file mode 100644
index 0000000000000000000000000000000000000000..ecdb652c5de590faede392fc0c269c44b9755df0
--- /dev/null
+++ b/gregui/api/serializers/guest.py
@@ -0,0 +1,21 @@
+from rest_framework import serializers
+
+from greg.models import Person
+
+
+class GuestRegisterSerializer(serializers.ModelSerializer):
+    def create(self, validated_data):
+        obj = super().create(validated_data)
+        return obj
+
+    class Meta:
+        model = Person
+        fields = ("id", "first_name", "last_name", "date_of_birth")
+        read_only_fields = ("id",)
+        extra_kwargs = {
+            "first_name": {"required": True},
+            "last_name": {"required": True},
+            "date_of_birth": {"required": True},
+            # 'email': {'required': True},
+            # 'phone_number': {'required': True},
+        }
diff --git a/gregui/api/urls.py b/gregui/api/urls.py
new file mode 100644
index 0000000000000000000000000000000000000000..0d39bbd34b782108f49262803e7722186bba22a7
--- /dev/null
+++ b/gregui/api/urls.py
@@ -0,0 +1,12 @@
+from django.urls import re_path
+
+from rest_framework.routers import DefaultRouter
+
+from gregui.api.views.guest import GuestRegisterView
+
+router = DefaultRouter(trailing_slash=False)
+
+urlpatterns = router.urls
+urlpatterns += [
+    re_path(r"register/$", GuestRegisterView.as_view(), name="guest-register"),
+]
diff --git a/gregui/api/views/__init__,py b/gregui/api/views/__init__,py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/gregui/api/views/guest.py b/gregui/api/views/guest.py
new file mode 100644
index 0000000000000000000000000000000000000000..4d9370203b94acd1563b9a8a1669964841721cc1
--- /dev/null
+++ b/gregui/api/views/guest.py
@@ -0,0 +1,10 @@
+from rest_framework import viewsets, filters, permissions
+from rest_framework.generics import CreateAPIView
+
+from greg.models import Person
+from gregui.api.serializers.guest import GuestRegisterSerializer
+
+class GuestRegisterView(CreateAPIView):
+    queryset = Person.objects.all()
+    permission_classes = [permissions.AllowAny]
+    serializer_class = GuestRegisterSerializer
\ No newline at end of file
diff --git a/gregui/tests.py b/gregui/tests.py
deleted file mode 100644
index 7ce503c2dd97ba78597f6ff6e4393132753573f6..0000000000000000000000000000000000000000
--- a/gregui/tests.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from django.test import TestCase
-
-# Create your tests here.
diff --git a/gregui/tests/__init__.py b/gregui/tests/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/gregui/tests/api/__init__.py b/gregui/tests/api/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/gregui/tests/api/test_guest.py b/gregui/tests/api/test_guest.py
new file mode 100644
index 0000000000000000000000000000000000000000..17102f7634fb8973f86a34d8e41715bb930025d4
--- /dev/null
+++ b/gregui/tests/api/test_guest.py
@@ -0,0 +1,13 @@
+import pytest
+
+from rest_framework import status
+from rest_framework.reverse import reverse
+
+
+@pytest.mark.django_db
+def test_register_guest(client):
+    data = {"first_name": "Foo", "last_name": "Bar", "date_of_birth": "2020-09-21"}
+    url = reverse("gregui-v1:guest-register")
+    response = client.post(url, data)
+    assert response.status_code == status.HTTP_201_CREATED
+    assert response.json() == {"id": 1, **data}
diff --git a/gregui/tests/conftest.py b/gregui/tests/conftest.py
new file mode 100644
index 0000000000000000000000000000000000000000..a59673226660f89957c51362bfc0499d0b546832
--- /dev/null
+++ b/gregui/tests/conftest.py
@@ -0,0 +1,22 @@
+from rest_framework.test import APIClient
+from django.contrib.auth import get_user_model
+
+import pytest
+
+from greg.models import (
+    Consent,
+    Notification,
+    Person,
+    Sponsor,
+    SponsorOrganizationalUnit,
+    Identity,
+    Role,
+    RoleType,
+    OrganizationalUnit,
+    ConsentType,
+)
+
+@pytest.fixture
+def client() -> APIClient:
+    client = APIClient()
+    return client
diff --git a/gregui/urls.py b/gregui/urls.py
new file mode 100644
index 0000000000000000000000000000000000000000..394acdecb7c466dfdb5c5bf79f715b4c6301ea0b
--- /dev/null
+++ b/gregui/urls.py
@@ -0,0 +1,10 @@
+from typing import List
+from django.urls import path, include
+from django.urls.resolvers import URLResolver
+from rest_framework.versioning import NamespaceVersioning
+
+from gregui.api import urls as api_urls
+
+urlpatterns: List[URLResolver] = [
+    path("api/ui/v1/", include((api_urls.urlpatterns, "gregui"), namespace="gregui-v1")),
+]