diff --git a/native-example/App.js b/native-example/App.js
index 3bffb49..091a52f 100644
--- a/native-example/App.js
+++ b/native-example/App.js
@@ -2,6 +2,7 @@
 import React, { useState, useEffect } from 'react';
 import { StyleSheet, Text, View, TextInput, Button } from 'react-native';
 import { useEasybase, EasybaseProvider } from 'easybase-react';
+import { NativeAuth } from 'easybase-react/native';
 import ebconfig from './ebconfig';
 
 function Account() {
@@ -57,7 +58,10 @@ function Router() {
 export default function app() {
   return (
     <EasybaseProvider ebconfig={ebconfig}>
-      <Router />
+      {/* <Router /> */}
+      <View style={styles.container}>
+        <NativeAuth />
+      </View>
     </EasybaseProvider>
   )
 }
diff --git a/native-example/package.json b/native-example/package.json
index c41bdf5..a89a389 100644
--- a/native-example/package.json
+++ b/native-example/package.json
@@ -5,6 +5,7 @@
     "ios": "npm run reinit && react-native run-ios",
     "web": "npm run reinit && expo start --web",
     "start": "npm run reinit && react-native start",
+    "resetCache": "react-native start --reset-cache",
     "reinit": "npm i github:easybase/easybase-react#dev"
   },
   "dependencies": {
diff --git a/native-example/yarn.lock b/native-example/yarn.lock
index 3e5cd27..a0ef3a1 100644
--- a/native-example/yarn.lock
+++ b/native-example/yarn.lock
@@ -2897,7 +2897,7 @@
   "version" "0.1.2"
 
 "easybase-react@github:easybase/easybase-react#dev":
-  "resolved" "git+ssh://git@github.com/easybase/easybase-react.git#bc356375b205adbde4872c3d3b3d2ba2452c73e2"
+  "resolved" "git+ssh://git@github.com/easybase/easybase-react.git#f43f356fb0873aa3a3a1a8c1f4c05541aa157be3"
   "version" "2.1.15"
   dependencies:
     "easybasejs" "4.2.13"
diff --git a/src/ui/NativeAuth/NativeAuth.tsx b/src/ui/NativeAuth/NativeAuth.tsx
new file mode 100644
index 0000000..570286e
--- /dev/null
+++ b/src/ui/NativeAuth/NativeAuth.tsx
@@ -0,0 +1,108 @@
+import React, { useEffect, useState, lazy, Suspense, Fragment } from 'react';
+import Container from './components/Container';
+import { ThemeProvider } from 'styled-components/native';
+import { Toaster } from 'react-hot-toast';
+import { mergeDeep, defaultDictionary } from '../utils';
+import { IStyles, IAuth } from '../uiTypes';
+import useEasybase from '../../useEasybase';
+
+const DefaultSignIn = lazy(() => import('./pages/SignIn'));
+const DefaultSignUp = lazy(() => import('./pages/SignUp'));
+const DefaultForgotPassword = lazy(() => import('./pages/ForgotPassword'));
+
+export default function ({ theme, customStyles, children, dictionary, signUpFields }: IAuth): JSX.Element {
+    const [themeVal, setThemeVal] = useState<any>({});
+
+    const [currentPage, setCurrentPage] = useState<"SignIn" | "SignUp" | "ForgotPassword" | "ForgotPasswordConfirm">("SignIn");
+    const { isUserSignedIn } = useEasybase();
+
+    useEffect(() => {
+        try {
+            document.body.style.margin = "0px";
+        } catch (_) { }
+        async function mounted() {
+            let loadedTheme: IStyles = {};
+            if (theme === "minimal-dark") {
+                const _theme = (await import('../themes/minimal-dark')).default;
+                if (_theme.init) {
+                    _theme.init()
+                }
+                loadedTheme = _theme;
+            } else if (theme === "material") {
+                const _theme = (await import('../themes/material')).default;
+                if (_theme.init) {
+                    _theme.init()
+                }
+                loadedTheme = _theme;
+            } else {
+                // catch all
+                const _theme = (await import('../themes/minimal')).default;
+                if (_theme.init) {
+                    _theme.init()
+                }
+                loadedTheme = _theme;
+            }
+
+            if (customStyles) {
+                loadedTheme = mergeDeep(loadedTheme, customStyles)
+            }
+
+            setThemeVal(loadedTheme)
+        }
+        mounted();
+    }, [theme])
+
+    if (isUserSignedIn()) {
+        return <Fragment>{children}</Fragment>
+    }
+
+    const getCurrentPage = () => {
+        switch (currentPage) {
+            case "SignIn":
+                return (
+                    <Suspense fallback={<Fragment />}>
+                        <DefaultSignIn
+                            setCurrentPage={setCurrentPage}
+                            dictionary={typeof dictionary === "object" ? { ...defaultDictionary, ...dictionary } : defaultDictionary}
+                        />
+                    </Suspense>
+                )
+            case "SignUp":
+                return (
+                    <Suspense fallback={<Fragment />}>
+                        <DefaultSignUp
+                            setCurrentPage={setCurrentPage}
+                            dictionary={typeof dictionary === "object" ? { ...defaultDictionary, ...dictionary } : defaultDictionary}
+                            signUpFields={typeof signUpFields === "object" ? signUpFields : {}}
+                        />
+                    </Suspense>
+                )
+            case "ForgotPassword":
+                return (
+                    <Suspense fallback={<Fragment />}>
+                        <DefaultForgotPassword
+                            setCurrentPage={setCurrentPage}
+                            dictionary={typeof dictionary === "object" ? { ...defaultDictionary, ...dictionary } : defaultDictionary}
+                        />
+                    </Suspense>
+                )
+            default:
+                return <React.Fragment />;
+        }
+    }
+
+    return (
+        <ThemeProvider theme={themeVal}>
+            <Container>
+                <Toaster toastOptions={{ style: { fontFamily: 'inherit', ...(themeVal.toast ? { ...themeVal.toast } : {}) } }} />
+                {/* {getCurrentPage()} */}
+                <Suspense fallback={<Fragment />}>
+                    <DefaultSignIn
+                        setCurrentPage={setCurrentPage}
+                        dictionary={typeof dictionary === "object" ? { ...defaultDictionary, ...dictionary } : defaultDictionary}
+                    />
+                </Suspense>
+            </Container>
+        </ThemeProvider>
+    )
+}
diff --git a/src/ui/NativeAuth/components/Container.tsx b/src/ui/NativeAuth/components/Container.tsx
new file mode 100644
index 0000000..9e5795c
--- /dev/null
+++ b/src/ui/NativeAuth/components/Container.tsx
@@ -0,0 +1,16 @@
+import React from 'react';
+import styled from 'styled-components/native';
+
+const Container = styled.View((props: any) => ({
+    flex: 1,
+    backgroundColor: '#fff',
+    alignItems: 'center',
+    justifyContent: 'center',
+    ...(props.theme.container ? { ...props.theme.container } : {})
+}))
+
+export default function (props: any) {
+    return (
+        <Container>{props.children}</Container>
+    )
+}
diff --git a/src/ui/NativeAuth/components/EmailInput.tsx b/src/ui/NativeAuth/components/EmailInput.tsx
new file mode 100644
index 0000000..915f178
--- /dev/null
+++ b/src/ui/NativeAuth/components/EmailInput.tsx
@@ -0,0 +1,8 @@
+import React from 'react';
+import Input from './internal/Input';
+
+export default function(props: any) {
+    return (
+        <Input label="Email" autoComplete="email" {...props} type="email" required />
+    )
+}
diff --git a/src/ui/NativeAuth/components/ErrorText.tsx b/src/ui/NativeAuth/components/ErrorText.tsx
new file mode 100644
index 0000000..fbd2c61
--- /dev/null
+++ b/src/ui/NativeAuth/components/ErrorText.tsx
@@ -0,0 +1,24 @@
+import React from 'react';
+import styled from 'styled-components';
+
+const ErrorText = styled.p(props => ({
+    marginTop: 5,
+    marginBottom: -5,
+    fontSize: 12,
+    fontWeight: 500,
+    color: 'red',
+    height: 0,
+    overflow: 'visible',
+    ...(props.theme.errorText ? { ...props.theme.errorText } : {})
+}))
+
+interface IErrorText extends React.HTMLAttributes<HTMLParagraphElement> {
+    value?: string | undefined;
+}
+
+export default function (props: IErrorText) {
+    if (props.value && props.value.length) {
+        return <ErrorText {...props}>{props.value}</ErrorText>
+    }
+    return <React.Fragment />
+}
diff --git a/src/ui/NativeAuth/components/ForgotPassword.tsx b/src/ui/NativeAuth/components/ForgotPassword.tsx
new file mode 100644
index 0000000..8aa3a6f
--- /dev/null
+++ b/src/ui/NativeAuth/components/ForgotPassword.tsx
@@ -0,0 +1,16 @@
+import React from 'react';
+import TextButton from './internal/TextButton';
+import styled from 'styled-components/native';
+
+const ForgotPassword = styled(TextButton)(props => ({
+    marginTop: -53,
+    marginBottom: 53,
+    display: 'flex',
+    ...(props.theme.forgotPassword ? { ...props.theme.forgotPassword } : {})
+}))
+
+export default function (props: any) {
+    return (
+        <ForgotPassword {...props} />
+    )
+}
diff --git a/src/ui/NativeAuth/components/Form.tsx b/src/ui/NativeAuth/components/Form.tsx
new file mode 100644
index 0000000..eaca678
--- /dev/null
+++ b/src/ui/NativeAuth/components/Form.tsx
@@ -0,0 +1,31 @@
+import React from 'react';
+import styled from 'styled-components/native';
+
+const Form = styled.View((props: any) => ({
+    display: "flex",
+    justifyContent: "center",
+    minWidth: 300,
+    width: 380,
+    padding: '33px 55px',
+    boxShadow: '0 5px 10px 0 rgb(0 0 0 / 10%)',
+    borderRadius: 10,
+    flexDirection: 'column',
+    fontFamily: "inherit",
+    margin: '6% auto 50px',
+    '@media (max-width: 520px)': {
+        margin: '0px !important',
+        position: 'fixed !important',
+        top: 0,
+        left: 0,
+        right: 0,
+        bottom: 0,
+        width: 'initial !important'
+    },
+    ...(props.theme.form ? { ...props.theme.form } : {})
+}))
+
+export default function (props: any) {
+    return (
+        <Form {...props}>{props.children}</Form>
+    )
+}
diff --git a/src/ui/NativeAuth/components/GenderSelect.tsx b/src/ui/NativeAuth/components/GenderSelect.tsx
new file mode 100644
index 0000000..9653a82
--- /dev/null
+++ b/src/ui/NativeAuth/components/GenderSelect.tsx
@@ -0,0 +1,27 @@
+import React from 'react';
+import { UseFormRegisterReturn } from 'react-hook-form';
+import styled from 'styled-components';
+import Label from './internal/Label';
+import Select from './internal/Select';
+
+const GenderSelect = styled(Select)(props => ({
+    boxSizing: "border-box",
+    ...(props.theme.genderSelect ? { ...props.theme.genderSelect } : {})
+}))
+
+const Root = styled.div({
+    position: "relative"
+})
+
+interface ISelect extends React.SelectHTMLAttributes<HTMLSelectElement> {
+    register(): UseFormRegisterReturn;
+}
+
+export default function (props: ISelect) {
+    return (
+        <Root>
+            <Label htmlFor="select-gender">Gender *</Label>
+            <GenderSelect id="select-gender" {...props} options={["Male", "Female", "Prefer not to say"]} />
+        </Root>
+    )
+}
diff --git a/src/ui/NativeAuth/components/HeaderText.tsx b/src/ui/NativeAuth/components/HeaderText.tsx
new file mode 100644
index 0000000..6ca22a4
--- /dev/null
+++ b/src/ui/NativeAuth/components/HeaderText.tsx
@@ -0,0 +1,21 @@
+import React from 'react';
+import styled from 'styled-components/native';
+
+const HeaderText = styled.Text((props: any) => ({
+    fontFamily: "inherit",
+    fontSize: 24,
+    fontWeight: 500,
+    letterSpacing: -.2,
+    marginBlockStart: '0.67em',
+    marginBlockEnd: '0.67em',
+    marginInlineStart: 0,
+    marginInlineEnd: 0,
+    marginTop: '16px !important',
+    ...(props.theme.headerText ? { ...props.theme.headerText } : {})
+}))
+
+export default function (props: any) {
+    return (
+        <HeaderText {...props} />
+    )
+}
diff --git a/src/ui/NativeAuth/components/PasswordInput.tsx b/src/ui/NativeAuth/components/PasswordInput.tsx
new file mode 100644
index 0000000..4123d31
--- /dev/null
+++ b/src/ui/NativeAuth/components/PasswordInput.tsx
@@ -0,0 +1,8 @@
+import React from 'react';
+import Input from './internal/Input';
+
+export default function(props: any) {
+    return (
+        <Input label="Password" {...props} type="password" required />
+    )
+}
diff --git a/src/ui/NativeAuth/components/SecondaryButton.tsx b/src/ui/NativeAuth/components/SecondaryButton.tsx
new file mode 100644
index 0000000..83cccea
--- /dev/null
+++ b/src/ui/NativeAuth/components/SecondaryButton.tsx
@@ -0,0 +1,14 @@
+import React from 'react';
+import TextButton from './internal/TextButton';
+import styled from 'styled-components/native';
+
+const SecondaryButton = styled(TextButton)(props => ({
+    margin: '15px',
+    ...(props.theme.secondaryButton ? { ...props.theme.secondaryButton } : {})
+}))
+
+export default function (props: any) {
+    return (
+        <SecondaryButton {...props} />
+    )
+}
diff --git a/src/ui/NativeAuth/components/SecondaryText.tsx b/src/ui/NativeAuth/components/SecondaryText.tsx
new file mode 100644
index 0000000..ba7b971
--- /dev/null
+++ b/src/ui/NativeAuth/components/SecondaryText.tsx
@@ -0,0 +1,18 @@
+import React from 'react';
+import styled from 'styled-components';
+
+const SecondaryText = styled.h2(props => ({
+    fontFamily: "inherit",
+    fontSize: 15,
+    fontWeight: 300,
+    letterSpacing: -.2,
+    lineHeight: '20px',
+    whiteSpace: 'normal',
+    ...(props.theme.secondaryText ? { ...props.theme.secondaryText } : {})
+}))
+
+export default function (props: React.HTMLAttributes<HTMLHeadingElement>) {
+    return (
+        <SecondaryText {...props} />
+    )
+}
diff --git a/src/ui/NativeAuth/components/Spacer.tsx b/src/ui/NativeAuth/components/Spacer.tsx
new file mode 100644
index 0000000..75456a2
--- /dev/null
+++ b/src/ui/NativeAuth/components/Spacer.tsx
@@ -0,0 +1,21 @@
+import React from 'react';
+import styled from 'styled-components/native';
+
+const Spacer = styled.View({});
+
+interface ISpacer {
+    size?: "xlarge" | "large" | "medium" | "small"
+}
+
+export default function (props: ISpacer) {
+    switch (props.size) {
+        case "xlarge":
+            return <Spacer style={{ height: 64 }} />   
+        case "large":
+            return <Spacer style={{ height: 58 }} />            
+        case "small":
+            return <Spacer style={{ height: 16 }} />
+        default:
+            return <Spacer style={{ height: 37 }} />
+    }
+}
diff --git a/src/ui/NativeAuth/components/SubmitButton.tsx b/src/ui/NativeAuth/components/SubmitButton.tsx
new file mode 100644
index 0000000..4df3d09
--- /dev/null
+++ b/src/ui/NativeAuth/components/SubmitButton.tsx
@@ -0,0 +1,25 @@
+import React from 'react';
+import styled from 'styled-components/native';
+
+const SubmitButtonRoot = styled.View((props: any) => props.theme.submitButtonRoot ? props.theme.submitButtonRoot : {});
+
+const SubmitButton = styled.Button((props: any) => ({
+    position: 'relative',
+    border: "none",
+    verticalAlign: "middle",
+    textAlign: "center",
+    textOverflow: "ellipsis",
+    overflow: "hidden",
+    outline: "none",
+    cursor: "pointer",
+    boxSizing: 'border-box',
+    ...(props.theme.submitButton ? { ...props.theme.submitButton } : {})
+}))
+
+export default function (props: any) {
+    return (
+        <SubmitButtonRoot>
+            <SubmitButton type="submit" {...props} />
+        </SubmitButtonRoot>
+    )
+}
diff --git a/src/ui/NativeAuth/components/internal/Input.tsx b/src/ui/NativeAuth/components/internal/Input.tsx
new file mode 100644
index 0000000..99a31c9
--- /dev/null
+++ b/src/ui/NativeAuth/components/internal/Input.tsx
@@ -0,0 +1,34 @@
+import React from 'react';
+import styled from 'styled-components/native';
+import Label from './Label';
+
+const TextFieldRoot = styled.View((props: any) => ({
+    position: 'relative',
+    width: '100%',
+    maxWidth: '100%',
+    padding: 0,
+    height: 46,
+    fontFamily: "inherit",
+    ...(props.theme.textFieldRoot ? { ...props.theme.textFieldRoot } : {})
+}))
+
+const TextField = styled.TextInput((props: any) => ({
+    display: "block",
+    width: '100%',
+    background: '0 0',
+    border: 'none',
+    fontFamily: "inherit",
+    ...(props.theme.textField ? { ...props.theme.textField } : {})
+}))
+
+const Bar = styled.View((props: any) => props.theme.textFieldBar ? { ...props.theme.textFieldBar } : {})
+
+export default function (props: any) {
+    return (
+        <TextFieldRoot>
+            <TextField placeholder="&nbsp;" {...props}/>
+            <Bar />
+            <Label>{props.label}</Label>
+        </TextFieldRoot>
+    )
+}
diff --git a/src/ui/NativeAuth/components/internal/Label.tsx b/src/ui/NativeAuth/components/internal/Label.tsx
new file mode 100644
index 0000000..9c35e37
--- /dev/null
+++ b/src/ui/NativeAuth/components/internal/Label.tsx
@@ -0,0 +1,12 @@
+import React from 'react';
+import styled from 'styled-components';
+
+const Label = styled.label(props => ({
+    display: "none",
+    fontFamily: "inherit",
+    ...(props.theme.textFieldLabel ? { ...props.theme.textFieldLabel } : {})
+}))
+
+export default function (props: React.LabelHTMLAttributes<HTMLLabelElement>) {
+    return (<Label {...props} />)
+}
diff --git a/src/ui/NativeAuth/components/internal/Select.tsx b/src/ui/NativeAuth/components/internal/Select.tsx
new file mode 100644
index 0000000..c87dde5
--- /dev/null
+++ b/src/ui/NativeAuth/components/internal/Select.tsx
@@ -0,0 +1,62 @@
+import React from 'react';
+import { UseFormRegisterReturn } from 'react-hook-form';
+import styled from 'styled-components';
+
+const SelectContainer = styled.div({
+    position: 'relative',
+    display: 'inline',
+    width: '100%',
+    maxWidth: '100%',
+    cursor: 'pointer',
+    '&:after': {
+        content: "''",
+        width: 0,
+        height: 0,
+        position: 'absolute',
+        pointerEvents: 'none',
+        top: '.3em',
+        right: '.75em',
+        borderTop: '8px solid black',
+        opacity: 0.5,
+        borderLeft: '5px solid transparent',
+        borderRight: '5px solid transparent'
+    }
+})
+
+const Select = styled.select({
+    WebkitAppearance: 'none',
+    MozAppearance: 'none',
+    appearance: 'none',
+    padding: '1em 2em 1em 1em',
+    border: 'none',
+    width: '100%',
+    fontFamily: 'inherit',
+    fontSize: 'inherit',
+    cursor: 'pointer',
+    outline: 'none',
+    '&::-ms-expand': {
+        display: 'none'
+    }
+})
+
+const SelectOption = styled.option(props => ({
+    width: '100%',
+    ...(props.theme.selectOption ? { ...props.theme.selectOption } : {})
+}))
+
+interface ISelect extends React.SelectHTMLAttributes<HTMLSelectElement> {
+    options: string[];
+    id: string;
+    register(): UseFormRegisterReturn;
+}
+
+export default function (props: ISelect) {
+    return (
+        <SelectContainer>
+            <Select {...props} {...props.register()} defaultValue="">
+                <SelectOption key="empty-option" value="" disabled hidden style={{ display: 'none' }} />
+                {props.options.map(e => <SelectOption key={"option" + e}>{e}</SelectOption>)}
+            </Select>
+        </SelectContainer>
+    )
+}
diff --git a/src/ui/NativeAuth/components/internal/TextButton.tsx b/src/ui/NativeAuth/components/internal/TextButton.tsx
new file mode 100644
index 0000000..2353369
--- /dev/null
+++ b/src/ui/NativeAuth/components/internal/TextButton.tsx
@@ -0,0 +1,20 @@
+import React from 'react';
+import styled from 'styled-components/native';
+
+const TextButton = styled.Button((props: any) => ({
+    cursor: "pointer",
+    color: '#635bff',
+    whiteSpace: 'nowrap',
+    fontWeight: 500,
+    fontSize: 14,
+    margin: 0,
+    background: 'none',
+    border: 'none',
+    ...(props.theme.textButton ? { ...props.theme.textButton } : {})
+}))
+
+export default function (props: any) {
+    return (
+        <TextButton {...props} />
+    )
+}
diff --git a/src/ui/NativeAuth/pages/ForgotPassword.tsx b/src/ui/NativeAuth/pages/ForgotPassword.tsx
new file mode 100644
index 0000000..6630741
--- /dev/null
+++ b/src/ui/NativeAuth/pages/ForgotPassword.tsx
@@ -0,0 +1,138 @@
+import React, { useState } from 'react';
+import Form from '../components/Form';
+import EmailInput from '../components/EmailInput';
+import HeaderText from '../components/HeaderText';
+import SecondaryText from '../components/SecondaryText';
+import SecondaryButton from '../components/SecondaryButton';
+import SubmitButton from '../components/SubmitButton';
+import Spacer from '../components/Spacer';
+import { useForm } from 'react-hook-form';
+import { IPage } from '../../uiTypes';
+import toast from 'react-hot-toast';
+import ErrorText from '../components/ErrorText';
+import Input from '../components/internal/Input';
+import PasswordInput from '../components/PasswordInput';
+import useEasybase from '../../../useEasybase';
+
+export default function ({ setCurrentPage, dictionary }: IPage) {
+    const [onConfirm, setOnConfirm] = useState<boolean>(false);
+    const [forgottenUsername, setForgottenUsername] = useState<string | undefined>();
+    const { register, handleSubmit, reset, formState: { errors, isSubmitting } } = useForm();
+    const { forgotPassword, forgotPasswordConfirm } = useEasybase();
+
+    const onSubmit = async (formData: Record<string, string>) => {
+        if (!formData.email) {
+            return;
+        }
+
+        const forgotRes = await forgotPassword(formData.email, { greeting: "Hello user," });
+        if (forgotRes.success) {
+            setForgottenUsername(formData.email);
+            setOnConfirm(true);
+            toast.success('Check your email for a verification code')
+        } else {
+            if (forgotRes.errorCode === "RequestLimitExceeded") {
+                toast.error(dictionary.errorRequestLimitExceeded!);
+            } else if (forgotRes.errorCode === "BadFormat") {
+                reset();
+                toast.error(dictionary.errorBadInputFormat!);
+            } else if (forgotRes.errorCode === "NoUserExists") {
+                reset();
+                toast.error(dictionary.errorNoAccountFound!);
+            } else {
+                reset();
+                toast.error('Bad request');
+            }
+        }
+    }
+
+    const onConfirmSubmit = async (formData: Record<string, string>) => {
+        if (!formData.code || !formData.newPassword || !forgottenUsername) {
+            return;
+        }
+        const forgotConfirmRes = await forgotPasswordConfirm(formData.code, forgottenUsername, formData.newPassword)
+        if (forgotConfirmRes.success) {
+            setOnConfirm(false);
+            setForgottenUsername("");
+            setCurrentPage('SignIn');
+            toast.success('Password successfully changed')
+        } else {
+            if (forgotConfirmRes.errorCode === "BadPasswordLength") {
+                toast.error(dictionary.errorPasswordTooShort!);
+            } else if (forgotConfirmRes.errorCode === "BadFormat") {
+                reset();
+                toast.error(dictionary.errorBadInputFormat!);
+            } else if (forgotConfirmRes.errorCode === "NoUserExists") {
+                reset();
+                toast.error(dictionary.errorNoAccountFound!);
+            } else if (forgotConfirmRes.errorCode === "WrongVerificationCode") {
+                toast.error(dictionary.errorWrongVerificationCode!);
+            } else {
+                toast.error('Bad request');
+            }
+        }
+    }
+
+    const passwordReqs = {
+        minLength: {
+            value: 8,
+            message: "Password must be at least 8 characters long"
+        },
+        maxLength: {
+            value: 100,
+            message: "Password too long"
+        },
+        pattern: {
+            value: /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[a-zA-Z]).{7,}$/gm,
+            message: "Must contain a digit and uppercase and lowercase letters"
+        }
+    }
+
+    const codeReqs = {
+        minLength: {
+            value: 8,
+            message: "Incorrect code length"
+        }
+    }
+
+    if (!onConfirm) {
+        return (
+            <Form onSubmit={handleSubmit(onSubmit)}>
+                <HeaderText>{dictionary.forgotPasswordHeader}</HeaderText>
+                <SecondaryText>{dictionary.forgotPasswordSecondaryHeader}</SecondaryText>
+                <Spacer size="medium" />
+                <EmailInput
+                    register={() => register("email")}
+                    label={dictionary.newEmailLabel}
+                    disabled={isSubmitting}
+                />
+                <Spacer size="medium" />
+                <SubmitButton disabled={isSubmitting}>{dictionary.forgotPasswordSubmitButton}</SubmitButton>
+                <SecondaryButton onClick={(_: any) => setCurrentPage("SignIn")} disabled={isSubmitting}>{dictionary.backToSignIn}</SecondaryButton>
+            </Form>
+        )
+    } else {
+        return (
+            <Form onSubmit={handleSubmit(onConfirmSubmit)}>
+                <HeaderText>{dictionary.forgotPasswordConfirmHeader}</HeaderText>
+                <Spacer size="medium" />
+                <Input
+                    register={() => register("code", codeReqs)}
+                    label={dictionary.codeLabel!}
+                    disabled={isSubmitting}
+                />
+                <ErrorText value={errors.code?.message} />
+                <Spacer size="xlarge" />
+                <PasswordInput
+                    register={() => register("newPassword", passwordReqs)}
+                    label={dictionary.forgotPasswordConfirmLabel}
+                    autoComplete="new-password"
+                    disabled={isSubmitting}
+                />
+                <ErrorText value={errors.newPassword?.message} />
+                <Spacer size="xlarge" />
+                <SubmitButton disabled={isSubmitting}>{dictionary.forgotPasswordConfirmSubmitButton}</SubmitButton>
+            </Form>
+        )
+    }
+}
diff --git a/src/ui/NativeAuth/pages/SignIn.tsx b/src/ui/NativeAuth/pages/SignIn.tsx
new file mode 100644
index 0000000..e674d5b
--- /dev/null
+++ b/src/ui/NativeAuth/pages/SignIn.tsx
@@ -0,0 +1,74 @@
+import React from 'react';
+import Form from '../components/Form';
+import EmailInput from '../components/EmailInput';
+import PasswordInput from '../components/PasswordInput';
+import HeaderText from '../components/HeaderText';
+import ForgotPassword from '../components/ForgotPassword';
+import SecondaryButton from '../components/SecondaryButton';
+import SubmitButton from '../components/SubmitButton';
+import Spacer from '../components/Spacer';
+import { useForm, Controller } from 'react-hook-form';
+import toast from 'react-hot-toast';
+import { IPage } from '../../uiTypes';
+import useEasybase from '../../../useEasybase';
+
+export default function ({ setCurrentPage, dictionary }: IPage) {
+    const { control, handleSubmit, reset, formState: { isSubmitting } } = useForm();
+    const { signIn } = useEasybase();
+    const onSubmit = async (formData: Record<string, string>) => {
+        await new Promise(resolve => setTimeout(resolve, 1000));
+        const signInRes = await signIn(formData.email, formData.password);
+        if (!signInRes.success) {
+            if (signInRes.errorCode === "NoUserExists") {
+                toast.error(dictionary.errorUserDoesNotExist!)
+            } else if (signInRes.errorCode === "BadFormat") {
+                reset();
+                toast.error(dictionary.errorBadInputFormat!)
+            }
+        }
+        // Will automatically change views
+    }
+
+    return (
+        <Form>
+            <HeaderText>{dictionary.signInHeader}</HeaderText>
+            <Spacer size="medium" />
+            <Controller
+                control={control}
+                render={({ field: { onChange, onBlur, value } }) => (
+                    <EmailInput
+                        onBlur={onBlur}
+                        onChangeText={(value: any) => onChange(value)}
+                        value={value}
+                        label={dictionary.emailLabel}
+                        disabled={isSubmitting}
+                    />
+                )}
+                name="email"
+                defaultValue=""
+            />
+
+            <Spacer size="xlarge" />
+            <Controller
+                control={control}
+                render={({ field: { onChange, onBlur, value } }) => (
+                    <PasswordInput
+                        onBlur={onBlur}
+                        onChangeText={(value: any) => onChange(value)}
+                        value={value}
+                        autoComplete="current-password"
+                        disabled={isSubmitting}
+                        label={dictionary.passwordLabel}
+                    />
+                )}
+                name="password"
+                defaultValue=""
+            />
+
+            <Spacer size="xlarge" />
+            <ForgotPassword onPress={(_: any) => setCurrentPage("ForgotPassword")} disabled={isSubmitting}>{dictionary.forgotPasswordButton}</ForgotPassword>
+            <SubmitButton onPress={handleSubmit(onSubmit)} disabled={isSubmitting}>{dictionary.signInSubmitButton}</SubmitButton>
+            <SecondaryButton onPress={(_: any) => setCurrentPage("SignUp")} disabled={isSubmitting}>{dictionary.noAccountButton}</SecondaryButton>
+        </Form>
+    )
+}
diff --git a/src/ui/NativeAuth/pages/SignUp.tsx b/src/ui/NativeAuth/pages/SignUp.tsx
new file mode 100644
index 0000000..63cdfe1
--- /dev/null
+++ b/src/ui/NativeAuth/pages/SignUp.tsx
@@ -0,0 +1,186 @@
+import React, { Fragment } from 'react';
+import Form from '../components/Form';
+import EmailInput from '../components/EmailInput';
+import PasswordInput from '../components/PasswordInput';
+import HeaderText from '../components/HeaderText';
+import SecondaryButton from '../components/SecondaryButton';
+import SubmitButton from '../components/SubmitButton';
+import Spacer from '../components/Spacer';
+import ErrorText from '../components/ErrorText';
+import GenderSelect from '../components/GenderSelect';
+import Input from '../components/internal/Input';
+import { useForm } from 'react-hook-form';
+import { IPage, ISignUpFields } from '../../uiTypes';
+import toast from 'react-hot-toast';
+import useEasybase from '../../../useEasybase';
+
+interface ISignUpPage extends IPage {
+    signUpFields: ISignUpFields
+}
+
+export default function ({ setCurrentPage, dictionary, signUpFields }: ISignUpPage) {
+    const { register, handleSubmit, formState: { errors, isSubmitting }, reset } = useForm();
+    const { signUp, signIn } = useEasybase();
+
+    const onSubmit = async (formData: Record<string, string>) => {
+        if (!formData.email || !formData.password || !formData.passwordConfirm) {
+            return;
+        }
+        if (formData.password !== formData.passwordConfirm) {
+            toast.error(dictionary.errorPasswordsDoNotMatch!);
+            reset();
+            return;
+        }
+
+        const signUpAttrs = { createdAt: new Date().toISOString() };
+        for (const currField of ["firstName", "lastName", "fullName", "dateOfBirth", "gender", "phoneNumber"]) {
+            if (signUpFields[currField]) {
+                if (formData[currField]) {
+                    signUpAttrs[currField] = "" + formData[currField];
+                } else {
+                    toast.error("Missing sign up field value");
+                    return;
+                }
+            }
+        }
+
+        const signUpRes = await signUp(formData.email, formData.password, signUpAttrs);
+        if (signUpRes.success) {
+            setCurrentPage("SignIn")
+            await signIn(formData.email, formData.password)
+        } else {
+            if (signUpRes.errorCode === "BadFormat") {
+                reset();
+                toast.error(dictionary.errorBadInputFormat!);
+            } else if (signUpRes.errorCode === "BadPasswordLength") {
+                toast.error(dictionary.errorPasswordTooShort!);
+            } else if (signUpRes.errorCode === "UserExists") {
+                reset();
+                toast.error(dictionary.errorUserAlreadyExists!);
+            }
+        }
+    }
+
+    const passwordReqs = {
+        minLength: {
+            value: 8,
+            message: "Password must be at least 8 characters long"
+        },
+        maxLength: {
+            value: 100,
+            message: "Password too long"
+        },
+        pattern: {
+            value: /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[a-zA-Z]).{7,}$/gm,
+            message: "Must contain a digit and uppercase and lowercase letters"
+        }
+    }
+
+    return (
+        <Form onSubmit={handleSubmit(onSubmit)}>
+            <HeaderText>{dictionary.signUpHeader}</HeaderText>
+            <Spacer size="medium" />
+
+            <EmailInput
+                register={() => register("email")}
+                label={dictionary.newEmailLabel}
+                disabled={isSubmitting}
+            />
+
+            { signUpFields.firstName &&
+                <Fragment>
+                    <Spacer size="xlarge" />
+                    <Input
+                        register={() => register("firstName", typeof signUpFields.firstName === "boolean" ? {} : signUpFields.firstName)}
+                        label={dictionary.newFirstNameLabel || ""}
+                        disabled={isSubmitting}
+                    />
+                    <ErrorText value={errors.firstName?.message} />
+                </Fragment>
+            }
+
+            { signUpFields.lastName &&
+                <Fragment>
+                    <Spacer size="xlarge" />
+                    <Input
+                        register={() => register("lastName", typeof signUpFields.lastName === "boolean" ? {} : signUpFields.lastName)}
+                        label={dictionary.newLastNameLabel || ""}
+                        disabled={isSubmitting}
+                    />
+                    <ErrorText value={errors.lastName?.message} />
+                </Fragment>
+            }
+
+            { signUpFields.fullName &&
+                <Fragment>
+                    <Spacer size="xlarge" />
+                    <Input
+                        register={() => register("fullName", typeof signUpFields.fullName === "boolean" ? {} : signUpFields.fullName)}
+                        label={dictionary.newFullNameLabel || ""}
+                        disabled={isSubmitting}
+                    />
+                    <ErrorText value={errors.fullName?.message} />
+                </Fragment>
+            }
+
+            { signUpFields.dateOfBirth &&
+                <Fragment>
+                    <Spacer size="xlarge" />
+                    <Input
+                        type="date"
+                        register={() => register("dateOfBirth", typeof signUpFields.dateOfBirth === "boolean" ? {} : signUpFields.dateOfBirth)}
+                        label={dictionary.newDateOfBirthLabel || ""}
+                        disabled={isSubmitting}
+                        style={{ overflow: "hidden" }}
+                    />
+                    <ErrorText value={errors.dateOfBirth?.message} />
+                </Fragment>
+            }
+
+            { signUpFields.gender &&
+                <Fragment>
+                    <Spacer size="xlarge" />
+                    <GenderSelect
+                        register={() => register("gender", typeof signUpFields.gender === "boolean" ? {} : signUpFields.gender)}
+                        disabled={isSubmitting}
+                    />
+                    <ErrorText value={errors.gender?.message} />
+                </Fragment>
+            }
+
+            { signUpFields.phoneNumber &&
+                <Fragment>
+                    <Spacer size="xlarge" />
+                    <Input
+                        type="tel"
+                        label={dictionary.newPhoneNumberLabel || ""}
+                        register={() => register("phoneNumber", typeof signUpFields.phoneNumber === "boolean" ? {} : signUpFields.phoneNumber)}
+                        disabled={isSubmitting}
+                    />
+                    <ErrorText value={errors.phoneNumber?.message} />
+                </Fragment>
+            }
+
+            <Spacer size="xlarge" />
+            <PasswordInput
+                register={() => register("password", passwordReqs)}
+                label={dictionary.newPasswordLabel}
+                autoComplete="new-password"
+                disabled={isSubmitting}
+            />
+            <ErrorText value={errors.password?.message} />
+            <Spacer size="xlarge" />
+            <PasswordInput
+                register={() => register("passwordConfirm", passwordReqs)}
+                label={dictionary.confirmNewPasswordLabel}
+                autoComplete="new-password"
+                disabled={isSubmitting}
+            />
+            <ErrorText value={errors.passwordConfirm?.message} />
+
+            <Spacer size="xlarge" />
+            <SubmitButton disabled={isSubmitting}>{dictionary.signUpSubmitButton}</SubmitButton>
+            <SecondaryButton onClick={(_: any) => setCurrentPage("SignIn")} disabled={isSubmitting}>{dictionary.backToSignIn}</SecondaryButton>
+        </Form>
+    )
+}
diff --git a/src/ui/ReactNative.tsx b/src/ui/ReactNative.tsx
index fc37838..58a8469 100644
--- a/src/ui/ReactNative.tsx
+++ b/src/ui/ReactNative.tsx
@@ -1,16 +1,18 @@
-import React from 'react';
+import React, { Suspense, Fragment, lazy } from 'react';
 import { IAuth } from './uiTypes';
-import styled from 'styled-components/native';
 
-const Button = styled.Button({
-    color: "red",
-    fontSize: 20
-})
+const NativeAuthComp = lazy(() => import('./NativeAuth/NativeAuth'));
 
-export function NativeAuth (props: IAuth): JSX.Element {
+export function NativeAuth(props: IAuth): JSX.Element {
     return (
-        <Button title="Cloc" onPress={console.log} />
+        <Suspense fallback={<Fragment />}>
+            <NativeAuthComp {...props} />
+        </Suspense>
     )
 }
 
 export default NativeAuth;
+
+/**
+ * Note that this wrapper component exists to force code-splitting
+ */