<rss version="2.0">
  <channel>
    <title>Forms on LLBBL Blog</title>
    <link>https://llbbl.blog/categories/forms/</link>
    <description></description>
    
    <language>en</language>
    
    <lastBuildDate>Sat, 11 Apr 2026 10:00:00 -0500</lastBuildDate>
    
    <item>
      <title>SvelteKit Forms: Back to Basics (And Why That&#39;s a Good Thing)</title>
      <link>https://llbbl.blog/2026/04/11/sveltekit-forms-back-to-basics.html</link>
      <pubDate>Sat, 11 Apr 2026 10:00:00 -0500</pubDate>
      
      <guid>http://llbbl.micro.blog/2026/04/11/sveltekit-forms-back-to-basics.html</guid>
      <description>&lt;p&gt;Before we talk about SvelteKit, we should touch on how React form handling works. I&amp;rsquo;m writing this to remind myself of the &lt;code&gt;modern&lt;/code&gt; way to do it&amp;hellip;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You set up &lt;code&gt;useState&lt;/code&gt; for every input field&lt;/li&gt;
&lt;li&gt;Write &lt;code&gt;onChange&lt;/code&gt; handlers to update state on every keystroke&lt;/li&gt;
&lt;li&gt;Add an &lt;code&gt;onSubmit&lt;/code&gt; handler to prevent the browser&amp;rsquo;s default behavior, construct a JSON payload, fire off a &lt;code&gt;fetch&lt;/code&gt; request&lt;/li&gt;
&lt;li&gt;Manage loading state to disable the submit button&lt;/li&gt;
&lt;li&gt;Manually parse the response to show success or error messages.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That&amp;rsquo;s a lot jumps to be hooped.&lt;/p&gt;
&lt;p&gt;Frameworks like Next.js have improved this with server actions, but the fundamental approach remains the same: a cage match with the browsers native form handling.&lt;/p&gt;
&lt;p&gt;SvelteKit takes a completely different approach. It just uses regular HTML.&lt;/p&gt;
&lt;h2 id=&#34;how-sveltekit-forms-work&#34;&gt;How SvelteKit Forms Work&lt;/h2&gt;
&lt;p&gt;In SvelteKit, you write a standard &lt;code&gt;&amp;lt;form method=&amp;quot;POST&amp;quot;&amp;gt;&lt;/code&gt; in your &lt;code&gt;+page.svelte&lt;/code&gt; file. That&amp;rsquo;s your UI. Then, in a &lt;code&gt;+page.server.ts&lt;/code&gt; file in the same directory, you export an &lt;code&gt;actions&lt;/code&gt; object that handles the submission.&lt;/p&gt;
&lt;p&gt;The server-side action receives a request object, extracts the form data, runs your logic, and returns either a &lt;code&gt;redirect&lt;/code&gt; or a &lt;code&gt;fail&lt;/code&gt; with validation errors. No client-side state management, no manual fetch calls, no JSON serialization.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-typescript&#34; data-lang=&#34;typescript&#34;&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// +page.server.ts
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;actions&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
  &lt;span style=&#34;color:#66d9ef&#34;&gt;default&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; ({ &lt;span style=&#34;color:#a6e22e&#34;&gt;request&lt;/span&gt; }) &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; {
    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;data&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;request&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;formData&lt;/span&gt;();
    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;email&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;data&lt;/span&gt;.&lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;email&amp;#39;&lt;/span&gt;);
    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;password&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;data&lt;/span&gt;.&lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;password&amp;#39;&lt;/span&gt;);

    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;email&lt;/span&gt;) {
      &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fail&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;400&lt;/span&gt;, { &lt;span style=&#34;color:#a6e22e&#34;&gt;email&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;missing&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt; });
    }

    &lt;span style=&#34;color:#75715e&#34;&gt;// authenticate user...
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;redirect&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;303&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;/dashboard&amp;#39;&lt;/span&gt;);
  }
};
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you&amp;rsquo;re a backend developer coming from Python or Ruby, this should feel familiar. The browser collects the data, posts it to a URL, and the server processes it. Stateless, reliable, and native.&lt;/p&gt;
&lt;h2 id=&#34;progressive-enhancement-the-best-of-both-worlds&#34;&gt;Progressive Enhancement: The Best of Both Worlds&lt;/h2&gt;
&lt;p&gt;You might be thinking, &amp;ldquo;Okay, but I don&amp;rsquo;t want full page reloads every time someone submits a form.&amp;rdquo; Fair point. SvelteKit handles this with something called progressive enhancement. You add &lt;code&gt;use:enhance&lt;/code&gt; to your form tag:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-svelte&#34; data-lang=&#34;svelte&#34;&gt;&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;form&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;method&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;POST&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;use:enhance&lt;/span&gt;&amp;gt;
  &lt;span style=&#34;color:#75715e&#34;&gt;&amp;lt;!-- your inputs --&amp;gt;&lt;/span&gt;
&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;form&lt;/span&gt;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That single directive tells SvelteKit to automatically intercept the submission, prevent the page reload, submit the data via fetch under the hood, and update the &lt;code&gt;form&lt;/code&gt; prop with any validation errors returned from the server.&lt;/p&gt;
&lt;p&gt;But wait there&amp;rsquo;s more.&lt;/p&gt;
&lt;p&gt;If JavaScript fails to load or the user is on a terrible connection, the form still works. It falls back to the browser&amp;rsquo;s native POST request.&lt;/p&gt;
&lt;p&gt;Your form doesn&amp;rsquo;t break just because a script didn&amp;rsquo;t load. That&amp;rsquo;s how the web is supposed to work.&lt;/p&gt;
&lt;p&gt;The React approach treats the browser as a rendering target and reimplements everything in JavaScript. SvelteKit treats the browser as a platform and builds on top of what&amp;rsquo;s already there.&lt;/p&gt;
&lt;p&gt;Now, don&amp;rsquo;t get me wrong. I&amp;rsquo;ll write React code any day of the week at the ole w2, but I am really liking how Svelte and SvelteKit has decided to approach these sorts of features.&lt;/p&gt;
</description>
    </item>
    
  </channel>
</rss>