Because we're using <form>
, our app works even if the user doesn't have JavaScript (which happens more often than you probably think). That's great, because it means our app is resilient.
Most of the time, users do have JavaScript. In those cases, we can progressively enhance the experience, the same way SvelteKit progressively enhances <a>
elements by using client-side routing.
Import the enhance
function from $app/forms
...
src/routes/+page.svelte
<script>
import { enhance } from '$app/forms';
export let data;
export let form;
</script>
...and add the use:enhance
directive to the <form>
elements:
src/routes/+page.svelte
<form method="POST" action="?/create" use:enhance>
src/routes/+page.svelte
<form method="POST" action="?/delete" use:enhance>
And that's all it takes! Now, when JavaScript is enabled, use:enhance
will emulate the browser-native behaviour except for the full-page reloads. It will:
- update the
form
prop - invalidate all data on a successful response, causing
load
functions to re-run - navigate to the new page on a redirect response
- render the nearest error page if an error occurs
Now that we're updating the page rather than reloading it, we can get fancy with things like transitions:
src/routes/+page.svelte
<script>
import { fly, slide } from 'svelte/transition';
import { enhance } from '$app/forms';
export let data;
export let form;
</script>
src/routes/+page.svelte
<li in:fly={{ y: 20 }} out:slide>...</li>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
<script>
export let data;
export let form;
</script>
<div class="centered">
<h1>todos</h1>
{#if form?.error}
<p class="error">{form.error}</p>
{/if}
<form method="POST" action="?/create">
<label>
add a todo:
<input
name="description"
value={form?.description ?? ''}
autocomplete="off"
required
/>
</label>
</form>
<ul class="todos">
{#each data.todos as todo (todo.id)}
<li>
<form method="POST" action="?/delete">
<input type="hidden" name="id" value={todo.id} />
<span>{todo.description}</span>
<button aria-label="Mark as complete"></button>
</form>
</li>
{/each}
</ul>
</div>
<style>
.centered {
max-width: 20em;
margin: 0 auto;
}
label {
width: 100%;
}
input {
flex: 1;
}
span {
flex: 1;
}
button {
border: none;
background: url(./remove.svg) no-repeat 50% 50%;
background-size: 1rem 1rem;
cursor: pointer;
height: 100%;
aspect-ratio: 1;
opacity: 0.5;
transition: opacity 0.2s;
}
button:hover {
opacity: 1;
}
.saving {
opacity: 0.5;
}
</style>