Svelte multi step form apps
Published: 2021-10-25 05:51:42

Today we are going to make a multistep form app with Svelte. So let's start.

First, let's make a basic template with `Start` and `Prev` buttons:
```html
<main>
  <div class="container">
    <div class="step-button">
      <button class="btn">Prev</button>
      <button class="btn">Next</button>
    </div>
  </div>
</main>

<style>
  @import url('https://fonts.googleapis.com/css?family=Muli&display=swap');

  * {
    box-sizing: border-box;
  }

  main {
    font-family: 'Muli', sans-serif;
    display: flex;
    align-items: center;
    justify-content: center;
    height: 100vh;
    overflow: hidden;
    margin: 0;
  }

  .btn {
    background-color: #3498db;
    color: #fff;
    border: 0;
    border-radius: 6px;
    cursor: pointer;
    font-family: inherit;
    padding: 8px 30px;
    margin: 5px;
    font-size: 14px;
  }

  .btn:active {
    transform: scale(0.98);
  }

  .btn:focus {
    outline: 0;
  }

  .btn:disabled {
    background-color: #e0e0e0;
    cursor: not-allowed;
  }

  .step-button{
    margin-top: 1rem;
    text-align: center;
  }
</style>
```

![Handle Button](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ydb9sitazqz6xnnfguaf.png)

Now we are going to build a steps progress bar component, So first create this `ProgressBar.svelte` component:

```html
<script>
  export let steps = ['Info', 'Address', 'Payment', 'Confirmation'];
</script>

<div class="progress-container">
  <div class="progress"></div>
  {#each steps as step, i}
    <div class="circle {i == 0 ? 'active' : ''}" data-title={step} >{i+1}</div>
  {/each}
</div>


<style>
  .progress-container {
    display: flex;
    justify-content: space-between;
    position: relative;
    margin-bottom: 30px;
    max-width: 100%;
    width: 350px;
  }

  .progress-container::before {
    content: '';
    background-color: #e0e0e0;
    position: absolute;
    top: 50%;
    left: 0;
    transform: translateY(-50%);
    height: 4px;
    width: 100%;
    z-index: -1;
  }

  .progress {
    background-color: #3498db;
    position: absolute;
    top: 50%;
    left: 0;
    transform: translateY(-50%);
    height: 4px;
    width: 0%;
    z-index: -1;
    transition: 0.4s ease;
  }

  .circle {
    background-color: #fff;
    color: #999;
    border-radius: 50%;
    height: 30px;
    width: 30px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 3px solid #e0e0e0;
    transition: 0.4s ease;
    cursor: pointer;
  }

  .circle::after{
    content: attr(data-title) " ";
    position: absolute;
    bottom: 35px;
    color: #999;
    transition: 0.4s ease;
  }

  .circle.active::after {
    color: #3498db;
  }

  .circle.active {
    border-color: #3498db;
  }
  </style>
```

![Progress Bar](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rsv7qn4i8szlarx7l7ro.png)

Our progress bar UI is ready, Now we need to implement functionality for the  `Step` panel and `Prev`, `Next` button.
The `Prev` button should be disabled for the first step `Info` and the `Next` button should be disabled for the last step `Confirmation`. So need to track the current active step `currentActive` and also for the handling progress we write a function `handleProgress` and for update the progress bar steps add a function `update` and declare `circles` for reference all steps elements and `progress` for progress bar element. So our latest `ProgressBar.svelte` version like this:

```html
<script>
  export let steps = [], currentActive = 1;
  let circles, progress;

  export const handleProgress = (stepIncrement) => {
    circles = document.querySelectorAll('.circle');
    if(stepIncrement == 1){
      currentActive++

      if(currentActive > circles.length) {
          currentActive = circles.length
      }
    } else {
      currentActive--

      if(currentActive < 1) {
          currentActive = 1
      }
    }


    update()
  }

  function update() {
    circles.forEach((circle, idx) => {
        if(idx < currentActive) {
            circle.classList.add('active')
        } else {
            circle.classList.remove('active')
        }
    })

    const actives = document.querySelectorAll('.active');

    progress.style.width = (actives.length - 1) / (circles.length - 1) * 100 + '%';
  }
</script>

<div class="progress-container" bind:this={circles}>
  <div class="progress" bind:this={progress}></div>
  {#each steps as step, i}
    <div class="circle {i == 0 ? 'active' : ''}" data-title={step} >{i+1}</div>
  {/each}
</div>
```

And update `App.svelte`:

```html
<script>
let steps = ['Info', 'Address', 'Payment', 'Confirmation'], currentActive = 1, progressBar;

const handleProgress = (stepIncrement) => {
  progressBar.handleProgress(stepIncrement)
}
</script>

<ProgressBar {steps} bind:currentActive bind:this={progressBar}/>

<button class="btn" on:click={() => handleProgress(-1)} disabled={currentActive == 1}>Prev</button>
<button class="btn" on:click={() => handleProgress(+1)} disabled={currentActive == steps.length}>Next</button>
```

![Progress Bar working](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g2qb2cx4hrxdd87c3r6m.gif)

Now we are going to make `Form.svelte` component, first, make an input component for reusability `InputField.svelte`.

```html
<script>
  export let value, label, type = 'text';

  function typeAction(node){
    node.type = type;
  }
</script>

<p class="form-control">
  {#if label}
    <label class="label" for>{label}:</label>
  {/if}
  <input use:typeAction class="input" bind:value={value}/>
</p>

<style>
  .form-control{
    margin: .5rem 0;
    text-align: left;
  }
  .input{
    width: 100%;
    display: block;
    padding: 0.5rem 0;
    margin-top: 0.5rem;
    border-width: 1px;
    border-radius: 0.25rem;
  }
</style>
```

Here we see a new directive `use:typeAction` its use for set dynamic input type. Ok `InputField` is ready  now move to `Form.svelte`:

```html
<script>
  import InputField from './InputField.svelte';
  export let active_step;
  let formData = {
    name: '',
    surname: '',
    email: '',
    password: '',
    address: '',
    city: '',
    country: '',
    postcode: '',
    account_name: '',
    card_no: ''
  }

  const handleSubmit = () => {
    console.log("Your form data => ",formData)
  }
</script>

<form class="form-container" on:submit={handleSubmit}>
  {#if active_step == 'Info'}
    <InputField label={'Name'} bind:value={formData.name}/>
    <InputField label={'Surname'} bind:value={formData.surname}/>
    <InputField label={'Email'} bind:value={formData.email}/>
    <InputField type={'password'} label={'Password'} bind:value={formData.password}/>
  {:else if active_step == 'Address'}
    <InputField label={'Address'} bind:value={formData.address}/>
    <InputField label={'City'} bind:value={formData.city}/>
    <InputField label={'Country'} bind:value={formData.country}/>
    <InputField label={'Postcode'} bind:value={formData.postcode}/>
  {:else if active_step == 'Payment'}
    <InputField label={'Account Name'} bind:value={formData.account_name}/>
    <InputField label={'Card No'} bind:value={formData.card_no}/>
  {:else if active_step == 'Confirmation'}
    <div class="message">
      <h2>Thank you for choosing us</h2>
      <button class="btn submit">Finish </button>
    </div>
  {/if}
</form>

<style>

.form-container {
  background-color: #fff;
  border-radius: 10px;
  box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1), 0 6px 6px rgba(0, 0, 0, 0.1);
  padding: 50px 20px;
  text-align: center;
  max-width: 100%;
  width: 350px;
}
.btn{
  color: white;
  padding: 0.5rem 0;
  margin-top: 0.5rem;
  display: inline-block;
  width: 100%;
  border-radius: 0.25rem;
  cursor:pointer;
}
.submit{
  background:linear-gradient(to bottom, #44c767 5%, #50b01c 100%);
  background-color:#44c767;
}
.submit:hover {
  background:linear-gradient(to bottom, #50b01c 5%, #44c767 100%);
  background-color:#50b01c;
}
.message{
  text-align: center;
}
</style>
```

And update `App.svelte`:

```html
<script>
  import Form from './Form.svelte';
  import ProgressBar from './ProgressBar.svelte';
  let steps = ['Info', 'Address', 'Payment', 'Confirmation'], currentActive = 1, progressBar;

  const handleProgress = (stepIncrement) => {
    progressBar.handleProgress(stepIncrement)
  }
</script>

<main>
  <div class="container">
    <ProgressBar {steps} bind:currentActive bind:this={progressBar}/>

    <Form active_step={steps[currentActive-1]}/>

    <div class="step-button">
      <button class="btn" on:click={() => handleProgress(-1)} disabled={currentActive == 1}>Prev</button>
      <button class="btn" on:click={() => handleProgress(+1)} disabled={currentActive == steps.length}>Next</button>
    </div>
  </div>
</main>
```

Here is the final output:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x6ijyzzggj1a0v0sacs4.gif)

[Svelte Expanding Card](https://dev.to/msisaifu/svelte-50-projects-50-days-project-1-expending-card-57nj)

Full source code in this [repl](https://svelte.dev/repl/7b05d57dcdc04f49be72844e4b2825b3?version=3.44.0).

You find me in [Github](https://github.com/msisaifu).