# New Contact Form Design | Full-Stack Google Contacts Clone with Adonis.js/Node.js and Quasar (Vue.js)

**Author:** Ndianabasi Udonkang  
**Published:** 2021-09-13

In this lesson, we will design the contact-creation form of our Google Contacts clone app. We will also learn how to create a route for the contact form and

---

## Tags

- [JavaScript](/llms/technical-blog/tag/javascript.md)
- [Vuejs](/llms/technical-blog/tag/vuejs.md)
- [Adonisjs](/llms/technical-blog/tag/adonisjs.md)
- [Quasar Framework](/llms/technical-blog/tag/quasar-framework.md)

## Part of Series: [Full-Stack Google Contacts Clone with Node.js (Adonisjs Framework) and Vue.js (Quasar Framework)](/llms/technical-blog/series/clnwu9bc991hiu3wo2vwmw45/full-stack-google-contacts-clone-with-node-js-adonisjs-framework-and-vue-js-quasar-framework.md)

This article is part of the **[Full-Stack Google Contacts Clone with Node.js (Adonisjs Framework) and Vue.js (Quasar Framework)](/llms/technical-blog/series/clnwu9bc991hiu3wo2vwmw45/full-stack-google-contacts-clone-with-node-js-adonisjs-framework-and-vue-js-quasar-framework.md)** series.

### Articles in this Series:

- [Introduction to Full-Stack Google Contacts Clone with Node.js (Adonisjs Framework) and Vuejs (Quasar Framework)](/llms/technical-blog/article/xe5mlxg43s9rpx1pxsvfdp3r/introduction-to-full-stack-google-contacts-clone-with-node-js-adonisjs-framework-and-vuejs-quasar-framework.md)
- [Workspace Setup | Full-Stack Google Contacts Clone with Adonis.js/Node.js and Quasar (Vue.js)](/llms/technical-blog/article/fo8str8yjpkz5dygxg875bpw/workspace-setup-full-stack-google-contacts-clone-with-adonis-js-node-js-and-quasar-vue-js.md)
- [Frontend Overview | Full-Stack Google Contacts Clone with Adonis.js/Node.js and Quasar (Vue.js)](/llms/technical-blog/article/nxgmng6fhf1cwjk30coflro2/frontend-overview-full-stack-google-contacts-clone-with-adonis-js-node-js-and-quasar-vue-js.md)
- [The Left Drawer | Full-Stack Google Contacts Clone with Adonis.js/Node.js and Quasar (Vue.js)](/llms/technical-blog/article/m6fagvy2il5h65llxxbbl9os/the-left-drawer-full-stack-google-contacts-clone-with-adonis-js-node-js-and-quasar-vue-js.md)
- [Improve The Header | Full-Stack Google Contacts Clone with Adonis.js/Node.js and Quasar (Vue.js)](/llms/technical-blog/article/jjwbxcg450jmkkcrrmkmwvwj/improve-the-header-full-stack-google-contacts-clone-with-adonis-js-node-js-and-quasar-vue-js.md)
- **New Contact Form Design | Full-Stack Google Contacts Clone with Adonis.js/Node.js and Quasar (Vue.js)** (Current Article)
- [Validating the Contact Form with Vuelidate | Full-Stack Google Contacts Clone with Adonis.js/Node.js and Quasar (Vue.js)](/llms/technical-blog/article/z0xr9m2ljler2cwnpsw2ycwa/validating-the-contact-form-with-vuelidate-full-stack-google-contacts-clone-with-adonis-js-node-js-and-quasar-vue-js.md)
- [Designing the Contacts Table | Full-Stack Google Contacts Clone with Adonis.js/Node.js and Quasar (Vue.js)](/llms/technical-blog/article/h2p1x1z406ib38zu2dci3jur/designing-the-contacts-table-full-stack-google-contacts-clone-with-adonis-js-node-js-and-quasar-vue-js.md)
- [Designing the Contacts Table (Part 2) | Full-Stack Google Contacts Clone with Adonis.js/Node.js and Quasar (Vue.js)](/llms/technical-blog/article/j0iscua9gg8ekuz8ifh6chpn/designing-the-contacts-table-part-2-full-stack-google-contacts-clone-with-adonis-js-node-js-and-quasar-vue-js.md)
- [Refactoring the Main Layout | Full-Stack Google Contacts Clone with Adonis.js/Node.js and Quasar (Vue.js)](/llms/technical-blog/article/uktmlqsivwtr3cvnpld59k9q/refactoring-the-main-layout-full-stack-google-contacts-clone-with-adonis-js-node-js-and-quasar-vue-js.md)
- [Improving User Experience with the Contacts Table | Full-Stack Google Contacts Clone with Adonis.js/Node.js and Quasar (Vue.js)](/llms/technical-blog/article/dzs812auf61ej7qu2cg726dh/improving-user-experience-with-the-contacts-table-full-stack-google-contacts-clone-with-adonis-js-node-js-and-quasar-vue-js.md)
- [Designing the Contact Details Page | Full-Stack Google Contacts Clone with Adonis.js/Node.js and Quasar (Vue.js)](/llms/technical-blog/article/kf9pz97n63fvgfrcy5viwzrg/designing-the-contact-details-page-full-stack-google-contacts-clone-with-adonis-js-node-js-and-quasar-vue-js.md)
- [Creating the Contact Edit Page | Full-Stack Google Contacts Clone with Adonis.js/Node.js and Quasar (Vue.js)](/llms/technical-blog/article/fmr90prj84c9zn0633jc368g/creating-the-contact-edit-page-full-stack-google-contacts-clone-with-adonis-js-node-js-and-quasar-vue-js.md)
- [How Software Backends Work | Full-Stack Google Contacts Clone with AdonisJs (Node.js) and Quasar Framework (Vue.js)](/llms/technical-blog/article/aca8fcrghz70nn1wqg3uzbum/how-software-backends-work-full-stack-google-contacts-clone-with-adonis-js-node-js-and-quasar-framework-vue-js.md)
- [Setting Up The Backend | Full-Stack Google Contacts Clone with AdonisJs (Node.js) and Quasar Framework (Vue.js)](/llms/technical-blog/article/fxb49fmz9ljbwrimbnnzudfv/setting-up-the-backend-full-stack-google-contacts-clone-with-adonis-js-node-js-and-quasar-framework-vue-js.md)
- [Why Choose the AdonisJs Framework? | Full-Stack Google Contacts Clone with AdonisJs (Node.js) and Quasar Framework (Vue.js)](/llms/technical-blog/article/lnhm5oudx34yo2869gmcms37/why-choose-the-adonis-js-framework-full-stack-google-contacts-clone-with-adonis-js-node-js-and-quasar-framework-vue-js.md)
- [Setting Up Our API Server with AdonisJs Framework | Full-Stack Google Contacts Clone with AdonisJs (Node.js) and Quasar Framework (Vue.js)](/llms/technical-blog/article/x1jgmet84aqj9qggu5mulkh0/setting-up-our-api-server-with-adonis-js-framework-full-stack-google-contacts-clone-with-adonis-js-node-js-and-quasar-framework-vue-js.md)
- [Setting Up Postman with the API Server | Full-Stack Google Contacts Clone with AdonisJS (Node.js) and Quasar Framework (Vue.js)](/llms/technical-blog/article/v4qehksislosmuy92qgjr4ct/setting-up-postman-with-the-api-server-full-stack-google-contacts-clone-with-adonis-js-node-js-and-quasar-framework-vue-js.md)
- [The Model-View-Controller Design Pattern in AdonisJS | Full-Stack Google Contacts Clone with AdonisJS (Node.js) and Quasar Framework (Vue.js)](/llms/technical-blog/article/h5wqql65iejop5xrplujw458/the-model-view-controller-design-pattern-in-adonis-js-full-stack-google-contacts-clone-with-adonis-js-node-js-and-quasar-framework-vue-js.md)
- [Create Column Definitions & Insert New Contacts | Full-Stack Google Contacts Clone with AdonisJS (Node.js) and Quasar Framework (Vue.js)](/llms/technical-blog/article/asc2emsho4u73r6jb2gxjddw/create-column-definitions-and-insert-new-contacts-full-stack-google-contacts-clone-with-adonis-js-node-js-and-quasar-framework-vue-js.md)
- [Data Validation & Sanitisation with AdonisJS | Full-Stack Google Contacts Clone with AdonisJS (Node.js) and Quasar Framework (Vue.js)](/llms/technical-blog/article/bcey0ac7m5sbs9hqy1h824sl/data-validation-and-sanitisation-with-adonis-js-full-stack-google-contacts-clone-with-adonis-js-node-js-and-quasar-framework-vue-js.md)
- [Creating a Middleware and Updating a Contact | Full-Stack Google Contacts Clone with AdonisJS (Node.js) and Quasar Framework (Vue.js)](/llms/technical-blog/article/oqxzmtgfy1r3oewpnedc7wf6/creating-a-middleware-and-updating-a-contact-full-stack-google-contacts-clone-with-adonis-js-node-js-and-quasar-framework-vue-js.md)
- [Fetching and Deleting a Contact with AdonisJS | Full-Stack Google Contacts Clone with AdonisJS Framework (Node.js) and Quasar Framework (Vue.js)](/llms/technical-blog/article/p6bh0ponq43u3vi6o8uww84x/fetching-and-deleting-a-contact-with-adonis-js-full-stack-google-contacts-clone-with-adonis-js-framework-node-js-and-quasar-framework-vue-js.md)
- [Using Model Factories and Seeders in AdonisJS | Full-Stack Google Contacts Clone with AdonisJS Framework (Node.js) and Quasar Framework (Vue.js)](/llms/technical-blog/article/cfotveuxbgjtdp12l6pzcb2y/using-model-factories-and-seeders-in-adonis-js-full-stack-google-contacts-clone-with-adonis-js-framework-node-js-and-quasar-framework-vue-js.md)
- [Creating and Registering a Vuex Module | Full-Stack Google Contacts Clone with AdonisJS Framework (Node.js) and Quasar Framework (Vue.js)](/llms/technical-blog/article/cw96ewnzujgsz2e2garhbuzb/creating-and-registering-a-vuex-module-full-stack-google-contacts-clone-with-adonis-js-framework-node-js-and-quasar-framework-vue-js.md)
- [Connecting UI Components to the Vuex Store | Full-Stack Google Contacts Clone with AdonisJS Framework (Node.js) and Quasar Framework (Vue.js)](/llms/technical-blog/article/fdyqp17yehbnqrjvjvab4b5b/connecting-ui-components-to-the-vuex-store-full-stack-google-contacts-clone-with-adonis-js-framework-node-js-and-quasar-framework-vue-js.md)
- [Connecting the Frontend to the API Server | Full-Stack Google Contacts Clone with AdonisJS Framework (Node.js) and Quasar Framework (Vue.js)](/llms/technical-blog/article/fk4262359gg3g7f0jn95fn9b/connecting-the-frontend-to-the-api-server-full-stack-google-contacts-clone-with-adonis-js-framework-node-js-and-quasar-framework-vue-js.md)
- [Creating and Updating Contacts From the Frontend | Full-Stack Google Contacts Clone with AdonisJS Framework (Node.js) and Quasar Framework (Vue.js)](/llms/technical-blog/article/t15w2s3ao9jo74l96stjo7z6/creating-and-updating-contacts-from-the-frontend-full-stack-google-contacts-clone-with-adonis-js-framework-node-js-and-quasar-framework-vue-js.md)
- [Uploading Files and Creating Avatars for Contacts With AdonisJS and Axios](/llms/technical-blog/article/hg9ilaubqesm8angg0rhrjp9/uploading-files-and-creating-avatars-for-contacts-with-adonis-js-and-axios.md)
- [Deleting a Contact with AdonisJS and Vuejs](/llms/technical-blog/article/zdr4pxmgcax2dsfnwimhty0g/deleting-a-contact-with-adonis-js-and-vuejs.md)

---

## Article Content

In this lesson, we will design the contact-creation form of our Google Contacts clone app. We will also learn how to create a route for the contact form and define the component (page) for the route. We will only focus on the UI/UX of the app in this lesson. The field validations and submission mechanisms will be discussed in subsequent lessons.

At the end of the lesson, your app should look like the video below.

%\[https://vimeo.com/603342241]

Start by creating a new branch of your project.

```bash
git checkout -b 03-contact-creation-form-design
```

### Change Overview

Three files will be modified:

1. `ui/src/css/app.scss` ([snapshot](https://github.com/ndianabasi/google-contacts/blob/cb75e20f9ca7299b2a96396e54c154bd9018c546/ui/src/css/app.scss))
2. `ui/src/layouts/MainLayout.vue` ([snapshot](https://github.com/ndianabasi/google-contacts/blob/cb75e20f9ca7299b2a96396e54c154bd9018c546/ui/src/layouts/MainLayout.vue))
3. `ui/src/router/routes.ts` ([snapshot](https://github.com/ndianabasi/google-contacts/blob/cb75e20f9ca7299b2a96396e54c154bd9018c546/ui/src/router/routes.ts))

Two files will be created:

1. `ui/src/pages/contacts/CreateContact.vue` ([snapshot](https://github.com/ndianabasi/google-contacts/blob/cb75e20f9ca7299b2a96396e54c154bd9018c546/ui/src/pages/contacts/CreateContact.vue))
2. `ui/src/types/index.ts` ([snapshot](https://github.com/ndianabasi/google-contacts/blob/cb75e20f9ca7299b2a96396e54c154bd9018c546/ui/src/types/index.ts))

### Create the `TypeScript` type definition file

The type definition file exports interfaces which will be used for type-safety throughout our application. For the current purpose, the `FormInterface` interface defines the form object with string indexes. Each index will be a `FormItem` with `label`, `required`, `value`, `inputType`, `icon`, and `autocomplete` properties. The `inputType`, `icon`, and `autocomplete` properties of the `FormItem` interface are optional. You can read more about `TypeScript` interfaces [here](https://www.typescriptlang.org/docs/handbook/interfaces.html).

Follow the steps below. Don't include the comments.

```bash
mkdir -p ui/src/types # Create the `types` folder
touch ui/src/types/index.ts # Create the `indext.ts` file
code ui/src/types/index.ts # Open the created file. For VS Code users
```

Copy and paste the content from  [this snapshot](https://github.com/ndianabasi/google-contacts/blob/cb75e20f9ca7299b2a96396e54c154bd9018c546/ui/src/types/index.ts) into the created `ui/src/types/index.ts` file. This file is imported into `CreateContact.vue` file and used to define the type for the `form` reactive variable inside the `setup` function.

### Create `CreateContact.vue`

1. Create the file. Follow the steps below. Don't include the comments.

   ```bash
   mkdir -p ui/src/pages/contacts # Create the `contacts` folder
   touch ui/src/pages/contacts/CreateContact.vue # Create the `CreateContact.vue` file
   code ui/src/pages/contacts/CreateContact.vue # Open the created file. For VS Code users
   ```

2. Open this [snapshot](https://github.com/ndianabasi/google-contacts/blob/cb75e20f9ca7299b2a96396e54c154bd9018c546/ui/src/layouts/MainLayout.vue) of `CreateContact.vue`. You can type out the content into the newly-created CreateContact.vue file or copy-and-paste the entire content from the snapshot.

   I will explain the content of the files in details later.

### Modify `ui/src/router/routes.ts`

Open `ui/src/router/routes.ts`. Update the `children` property of the `"/"` route object to:

```ts
children: [
   {
     path: "",
     name: "home",
     component: () => import("pages/Index.vue"),
     meta: { title: "Home" },
   },
   {
     path: "/contacts/new",
     name: "new_contact",
     component: () => import("pages/contacts/CreateContact.vue"),
     meta: { title: "New Contact" },
   },
 ]
```

You might notice that new properties have been added to the children routes, namely: `name` and `meta` property. The `name` property is a standard property for `Vue Router` routes. It is used to name each route and will be used for navigating to them. So, instead of navigating to the contact-creation page with `$router.push(path: "/contacts/new");`, you can use: `$router.push(name: "new_contact");`. The advantage of using route names for navigation is that the `path` strings for routes could be updated or the nesting levels could change, but if the `name` property is the same, you won't have to update the router-navigation calls within your application.

Additionally, there is the `meta` property. The `meta` property is a custom property. It could be called anything else. As a matter of fact, you can define extra properties on each route object as you wish and they will be available on `$route` object for each route you are currently on. In this case, the `meta` property is used to define the `title` property which is the title of each page. This title could be used to update the page title later, but for now, it is used to display the page title within a `q-toolbar` component added within the `q-page-container`. See these lines in [MainLayout.vue](https://github.com/ndianabasi/google-contacts/blob/cb75e20f9ca7299b2a96396e54c154bd9018c546/ui/src/layouts/MainLayout.vue#L219-L226). The `q-toolbar` is not rendered on the `home` route.

Most importantly, there is a new route object for which defines the component for the contact-creation page.

```ts
children: [
   ...
   {
     path: "/contacts/new",
     name: "new_contact",
     component: () => import("pages/contacts/CreateContact.vue"),
     meta: { title: "New Contact" },
   },
 ]
```

The component is loaded with an asynchronous import: `component: () => import("pages/contacts/CreateContact.vue")`. Asynchronous imports ensures that route components are only loaded when you request to navigate into that page/route. It helps speed up the initial load time of your application and is very important for large applications with tens to hundreds of route definitions. This is known as route lazy-loading. Read more [here](https://router.vuejs.org/guide/advanced/lazy-loading.html#lazy-loading-routes).

### Add toolbar for page title in `MainLayout.vue`

Open `ui/src/layouts/MainLayout.vue` and add the following from Line 219. Or copy-and-paste these [lines](https://github.com/ndianabasi/google-contacts/blob/dd5cbff78c529a673b5a8777ec094d028debe383/ui/src/layouts/MainLayout.vue#L219-L226).

```diff
    <q-page-container class="GPL__page-container">
+      <q-toolbar
+        v-if="$route.name !== 'home'"
+        class="text-primary q-mt-sm sticky-top"
+      >
+        <q-toolbar-title class="text-center">
+          {{ $route.meta.title }}
+        </q-toolbar-title>
+      </q-toolbar>
      <router-view />
    </q-page-container>
```

The only significant thing in the above code is that the route title is interpolated with the call: `$route.meta.title`. As mentioned earlier, we ensure that this toolbar doesn't appear on the `home` route with the `v-if` directive: `v-if="$route.name !== 'home'"`.

At this stage, you should be able to navigate to the `new_contact` route. Visit: http://localhost:8008/#/contacts/new. This should load the contact-creation form.

You might notice that the `q-input` component for the `birthday` field isn't styled properly. This is the default behaviour from Quasar for `q-input` with the `date` type. Let's improve the styling for our purpose. Open the `ui/src/css/app.scss` file and paste these lines:

```css
input[type="date"] ~ div.q-field__label {
  margin-top: -0.75rem;
}
```

Don't forget to save your files.

Now, let's look at what's going on within the `CreateContact.vue` file. Please refer to [this snapshot](https://github.com/ndianabasi/google-contacts/blob/cb75e20f9ca7299b2a96396e54c154bd9018c546/ui/src/pages/contacts/CreateContact.vue).

Beginning from the `script` section. At Line 62, the reactive `form` object is defined and initialised with an object field definitions. The `form` object will be used as the schema to autogenerate the form fields instead of duplicating the `q-input` component. (I gave a lot of thoughts before deciding to autogenerate the fields. **As a software engineer, you must always DRY (Don't Repeat Yourself) your codes, i.e. above repetitions where possible**. Even though this is a tutorials, the `template` section will be very difficult to understand and maintain if the `q-input` component is repeated and all attributes hardcoded in the template. The mere thought of that makes me shudder.)

Continuing, the `form` object is typed with the `FormInterface` interface from `ui/src/types/index.ts`. So errors will be thrown if the types are not compatible. Each field definition should have six properties: `label`, `required`, `value`, `inputType`, `icon`, and `autocomplete`. The last three are optional properties. Most notably is the `autocomplete` property which should be defined so that each field can have the proper suggestions when you are typing. The `autocomplete` value will be mapped to the `autocomplete attribute` of each field. You can read more about the [HTML autocomplete attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete). Refer to the [official specifications for autocomplete](https://html.spec.whatwg.org/multipage/#attr-fe-autocomplete) too.

The `setup` function returns an object with three properties: `form`, `dense`, and the `submitForm` function. The dense property is set to true when the screen is less than the `sm` breakpoint. This ensures that the form is condensed for very small devices. The `form` property will be used as the schema for generating the form fields. The `submitForm` isn't functional yet.

Within the `template` section, we have a `q-page` styled as a flex row and centered horizontally with `justify-center` and a large top-margin.
Then there is a `div` with responsive classes with ensures that the form displays well on different screen sizes. Then a `q-form` component which wraps all the `q-input` components which will be generated with the `v-for` directive. It is important to always use a `q-form` component or the bare `form` tag to wrap multiple form fields. The `q-form` can have a `@submit` event listener which can improve user experience during form submission.

```html
        <q-input
          v-for="({ label, icon, inputType, autocomplete }, key) in form"
          :key="key"
          :for="`${key}_${inputType || 'text'}_input`"
          bottom-slots
          v-model="form[key].value"
          :label="label"
          :dense="dense"
          :class="!icon && 'q-pl-lg'"
          :type="inputType || 'text'"
          :autogrow="inputType === 'textarea'"
          :autofocus="key === 'firstName'"
          :aria-autocomplete="autocomplete"
          :autocomplete="autocomplete"
        >
          <template v-slot:before>
            <q-icon v-if="icon" :name="icon" />
          </template>

          <template v-slot:after>
            <q-icon
              v-if="form[key].value"
              name="close"
              @click="form[key].value = ''"
              class="cursor-pointer"
            />
          </template>
        </q-input>

```

The [`q-input` component](https://quasar.dev/vue-components/input) is a Quasar component for creating the `input` tag. It is laced with various props and slots to customise its behaviour and improve the user experience. Most importantly, a `v-for` directive is used to loop over the `form` object exported from the `setup` function within the `script` section. The `v-for` directive could have looked like this: `v-for="(value, key) in form"`. `value` is basically each field definition and `key` is the string `index` of each object (entry) in the `form` object. So we can destructure `value` to expose the properties of the fields within the scope of the `q-input` component.

Some important props defined on `q-input` include:

1. The `for` prop is used to define the value of the `id` attribute of the `input` tag and the `for` attribute of the `label` tag for the `input` tag. In this case, the `for` prop is derived from the `key`, `inputType` scoped variables. The `id` and `for` attributes should always match for each field set.
2. The `bottom-slots` prop is used to enable the display of the `hint` and `error` slots for the `q-input` component. The `error` slot will be used in subsequent lessons to display validation errors.
3. The `label` prop is used for defining the label for the field.
4. The `dense` prop determines if the form fields will be condensed or not. It is useful in small screens to reduce the height of forms.
5. The `type` prop sets the type of the `input` tag.
6. The `autogrow` prop is set to true when the `inputType` variable is strictly-equal to `textarea`.
7. The `autofocus` prop is used to focus the cursor within the `firstName` field when the form is rendered.
8. The `aria-autocomplete` and `autocomplete` props set the `autocomplete` and `aria-autocomplete` attributes necessary for useful browser suggestions and autofills.

Two slots are consumed within the `q-input` component: `before` and `after` slots. They are used to display the icons before each field and the `close` icon after each for resetting each field.

If you have followed carefully, you should have the `New Contact` form well-rendered.

Save all your files, commit and merge with the master branch.

```bash
git add .
git commit -m "feat(ui): complete new contact form design"
git push --set-upstream origin 03-contact-creation-form-design
git checkout master
git merge master 03-contact-creation-form-design
git push

```

That's all for now. In the next lesson, you will learn how to perform form validations.


*This document was generated from the live article page on https://ndianabasi.com/technical-blog/article/ebguwpkntk3empakunrki4dq/new-contact-form-design-full-stack-google-contacts-clone-with-adonis-js-node-js-and-quasar-vue-js • 2026-06-07*
