<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Eelco from Spinal</title>
    <description>The latest articles on DEV Community by Eelco from Spinal (@eelco_spinal).</description>
    <link>https://dev.to/eelco_spinal</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F989245%2Fbf113ef5-7822-46c3-976d-6b12a6150edc.png</url>
      <title>DEV Community: Eelco from Spinal</title>
      <link>https://dev.to/eelco_spinal</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/eelco_spinal"/>
    <language>en</language>
    <item>
      <title>Conditionally style elements in Turbo Frames with Tailwind Css</title>
      <dc:creator>Eelco from Spinal</dc:creator>
      <pubDate>Tue, 23 May 2023 09:07:52 +0000</pubDate>
      <link>https://dev.to/spinal/conditionally-style-elements-in-turbo-frames-with-tailwind-css-1d74</link>
      <guid>https://dev.to/spinal/conditionally-style-elements-in-turbo-frames-with-tailwind-css-1d74</guid>
      <description>&lt;p&gt;Part of &lt;a href="https://turbo.hotwired.dev/" rel="noopener noreferrer"&gt;Hotwire&lt;/a&gt; are &lt;a href="https://turbo.hotwired.dev/handbook/frames" rel="noopener noreferrer"&gt;Turbo Frames&lt;/a&gt;. They allow you to insert a page, or just a part of it, into another page. &lt;/p&gt;

&lt;p&gt;I've found this feature quite magical when it was released. 🪄&lt;/p&gt;

&lt;p&gt;But it would be useful to apply some Css conditionally, based if the page was viewed standalone or within a turbo frame. &lt;/p&gt;

&lt;p&gt;Previously, and still valid, way to do this was adding a wrapper/container class around the turbo-frame and nested the css within here. &lt;/p&gt;

&lt;p&gt;With Tailwind Css this is easier to do. Let's take the following example from &lt;a href="https://helptail.com/" rel="noopener noreferrer"&gt;Helptail&lt;/a&gt; (Spinal's internally developed email support tool for calm SaaS companies):&lt;/p&gt;

&lt;p&gt;This is the email composer on a standalone page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F18e1dpps7b9cns52b24m.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F18e1dpps7b9cns52b24m.jpg" alt="The standalone page with the email composer"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The the same page, but within a turbo frame:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7ksq13290ys7quanin4q.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7ksq13290ys7quanin4q.jpg" alt="The email composer shown in a turbo-frame"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice how certain elements are not shown and some things look different. The changes in this example are basic for demonstration purposes, but you can imagine making it look completely different for each variant (similar how you would with the dark-variant).&lt;/p&gt;



&lt;h2&gt;
  
  
  How to do this
&lt;/h2&gt;

&lt;p&gt;Set up a new variant in your Tailwind Css' config file, like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="c1"&gt;// …&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="c1"&gt;// …&lt;/span&gt;
    &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;addVariant&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;addVariant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;turbo-frame&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;turbo-frame[src] &amp;amp;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="c1"&gt;// …&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How it works: &lt;code&gt;turbo-frame[src]&lt;/code&gt; matches a turbo-frame with any value for the &lt;code&gt;src&lt;/code&gt; attribute. Check this &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors#syntax" rel="noopener noreferrer"&gt;mdn&lt;/a&gt; article for all the details. And refer to the &lt;a href="https://v2.tailwindcss.com/docs/plugins#adding-variants" rel="noopener noreferrer"&gt;Tailwind docs&lt;/a&gt; about the &lt;code&gt;addVariant&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;Note: you can change the variant name to something else than &lt;code&gt;turbo-frame&lt;/code&gt;, like &lt;code&gt;inside-frame&lt;/code&gt; for example.&lt;/p&gt;

&lt;p&gt;You now have the &lt;code&gt;turbo-frame:&lt;/code&gt;-variant. It can be used in the same way as &lt;code&gt;md:&lt;/code&gt;, &lt;code&gt;dark:&lt;/code&gt;, etc.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"block turbo-frame:hidden"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"from"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;From&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So above &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;-element will be visible on the standalone page and be hidden when shown within a turbo-frame. &lt;/p&gt;

&lt;p&gt;And that is all there's to it. You can now create one page that can look completely different from when loaded in a turbo-frame. &lt;/p&gt;

</description>
      <category>hotwire</category>
      <category>tailwindcss</category>
      <category>rails</category>
    </item>
    <item>
      <title>A quality of life Stimulus.js controller</title>
      <dc:creator>Eelco from Spinal</dc:creator>
      <pubDate>Fri, 10 Feb 2023 13:16:02 +0000</pubDate>
      <link>https://dev.to/spinal/a-quality-of-life-stimulusjs-controller-391j</link>
      <guid>https://dev.to/spinal/a-quality-of-life-stimulusjs-controller-391j</guid>
      <description>&lt;p&gt;If you are developing any (Rails) app, you know how often have to log in to your own application. Enter your email and password for the umpteenth time isn't really 2023 is it? You could store them in your password manager, sure. But that also that is too much work. Besides you don't really want to clutter your password manager with dummy or seed data.&lt;/p&gt;

&lt;p&gt;For the past few years, at least since &lt;a href="https://stimulus.hotwired.dev/" rel="noopener noreferrer"&gt;Stimulus.js was introduced&lt;/a&gt;, I've been using this simple controller for all the SaaS products I developed. &lt;/p&gt;

&lt;p&gt;Here it is, ready to be copied-and-pasted. Let's go over some of the more interesting bits afterwards.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;app/javascript/controllers/development/fill_form_controller.js&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Controller&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@hotwired/stimulus&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Controller&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;targets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;formElementsOnPage&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;devEnvironment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nf"&gt;addComponent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;analysePage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;formElementsOnPage&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nf"&gt;fillForm&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;buttonTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Too many forms on this page…&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;analysePageWithKeybinding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;f&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;analysePage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// private&lt;/span&gt;

  &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nf"&gt;formElementsOnPage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;numberOfFormElements&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nf"&gt;devEnvironment&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODE_ENV&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;development&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nf"&gt;addComponent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insertAdjacentHTML&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;beforeend&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nf"&gt;buttonTag&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nf"&gt;numberOfFormElements&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementsByTagName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;form&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nf"&gt;buttonTag&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`
      &amp;lt;button
        data-development--fill-form-target="button"
        data-action="development--fill-form#analysePage keydown@window-&amp;gt;development--fill-form#analysePageWithKeybinding"
      &amp;gt;
        Fill form
      &amp;lt;/button&amp;gt;
    `&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nf"&gt;fillForm&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;forms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementsByTagName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;form&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="p"&gt;[].&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;forms&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nf"&gt;fillInputsFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nf"&gt;fillInputsFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;inputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementsByTagName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;input&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="p"&gt;[].&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;regex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;^.*&lt;/span&gt;&lt;span class="se"&gt;\[&lt;/span&gt;&lt;span class="sr"&gt;|&lt;/span&gt;&lt;span class="se"&gt;\]&lt;/span&gt;&lt;span class="sr"&gt;.*$&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;inputName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inputName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nf"&gt;randomiseValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nf"&gt;nameValueOptions&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nf"&gt;randomiseValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nf"&gt;emailValueOptions&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;password&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nf"&gt;randomiseValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nf"&gt;passwordValueOptions&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nf"&gt;nameValueOptions&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Chris&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Kate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Cameron&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nf"&gt;emailValueOptions&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;test@example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nf"&gt;passwordValueOptions&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;password&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nf"&gt;randomiseValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then in &lt;strong&gt;app/view/layouts/application.html.erb&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;div&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;data: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;controller: &lt;/span&gt;&lt;span class="s2"&gt;"development--fill-form"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;development?&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You could also only add it to the layout for your sign up/log in instead. It's what I do. &lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;All this class does is check if there's one form present on the page. If there's one (and not many), and the user presses &lt;code&gt;f&lt;/code&gt; or the button in the bottom-right, the form fields for &lt;strong&gt;name&lt;/strong&gt;, &lt;strong&gt;email&lt;/strong&gt; and &lt;strong&gt;password&lt;/strong&gt; gets, randomly, filled with any of the values from any of the &lt;code&gt;_ValueOptions()&lt;/code&gt; functions.&lt;/p&gt;

&lt;p&gt;Let's go over some of the more interesting bits.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;formElementsOnPage&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;devEnvironment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nf"&gt;addComponent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If there are form elements on the page and if the environment equals "development", run &lt;code&gt;this#addComponent()&lt;/code&gt;. The environment check is also done when adding the element on the page with &lt;code&gt;Rails.env.development?&lt;/code&gt;. It really is just an extra safeguard. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;Quick side note&lt;/em&gt;: the functions with &lt;code&gt;#&lt;/code&gt; prefix are “&lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private_class_fields" rel="noopener noreferrer"&gt;private functions&lt;/a&gt;”. Similar to Ruby's private methods. I like to expose only the functions needed for the functionality to work. The &lt;code&gt;// private&lt;/code&gt; comment does nothing really, I like it for a visual cue.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nf"&gt;buttonTag&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`
   &amp;lt;button
     data-development--fill-form-target="button"
      data-action="development--fill-form#analysePage keydown@window-&amp;gt;development--fill-form#analysePageWithKeybinding"
      &amp;gt;
      Fill form
    &amp;lt;/button&amp;gt;
  `&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This isn't a method I use often with Stimulus.js (which works best with HTML you have), but sometimes adding the HTML from the controller makes sense.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nf"&gt;fillInputsFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// …&lt;/span&gt;

&lt;span class="p"&gt;[].&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;regex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;^.*&lt;/span&gt;&lt;span class="se"&gt;\[&lt;/span&gt;&lt;span class="sr"&gt;|&lt;/span&gt;&lt;span class="se"&gt;\]&lt;/span&gt;&lt;span class="sr"&gt;.*$&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;inputName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inputName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// …&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// …&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function checks for the input's name value and inserts a random value from any of the &lt;code&gt;_ValueOptions()&lt;/code&gt;. A tricky bit here is that Rails' expects input name's like &lt;code&gt;model[name]&lt;/code&gt;, so this is taken care off with some regex. &lt;/p&gt;

&lt;p&gt;If you want to add more values to the current options, you can expand the arrays. Also if you need more fields, you can extend them easily.&lt;/p&gt;

&lt;p&gt;Like this approach? Or do you know a better way? Do let me know.&lt;/p&gt;

</description>
      <category>ui</category>
      <category>frontend</category>
      <category>webdev</category>
      <category>discuss</category>
    </item>
    <item>
      <title>The best hosting options for your static site (for 2023)</title>
      <dc:creator>Eelco from Spinal</dc:creator>
      <pubDate>Wed, 25 Jan 2023 11:58:14 +0000</pubDate>
      <link>https://dev.to/spinal/the-best-hosting-options-for-your-static-site-for-2023-2p17</link>
      <guid>https://dev.to/spinal/the-best-hosting-options-for-your-static-site-for-2023-2p17</guid>
      <description>&lt;p&gt;You have built a great marketing site using one of the many &lt;a href="https://spinalcms.com/built-for/"&gt;static site generators&lt;/a&gt;, like &lt;a href="https://spinalcms.com/cms-for-jekyll/"&gt;Jekyll&lt;/a&gt;, &lt;a href="https://spinalcms.com/cms-for-11ty/"&gt;Eleventy&lt;/a&gt; or &lt;a href="https://spinalcms.com/cms-for-astro/"&gt;Astro&lt;/a&gt;, for your SaaS. Awesome. But it isn't real, unless you put it out there for the world to see.&lt;/p&gt;

&lt;p&gt;There are many options available for hosting a static website, and the best choice will depend on your specific needs and budget. Here are a few providers that offer hosting for static websites, along with some pros and cons for each. For example, not all provide Git integration out-of-the-box. Which means extra work from your side to get that buttery-smooth publishing (push-to-deploy) workflow (that also makes &lt;a href="https://spinalcms.com/"&gt;Spinal&lt;/a&gt; so magical to use).&lt;/p&gt;

&lt;h2&gt;
  
  
  Netlify (Spinal's 2023 recommendation)
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tUhrJJ13--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bo5mjlldjnvilspsa70u.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tUhrJJ13--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bo5mjlldjnvilspsa70u.jpg" alt="Netlify logo" width="300" height="100"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.netlify.com/"&gt;Netlify&lt;/a&gt; is a popular choice for hosting static websites, with a range of pricing plans to suit different needs. It offers fast, reliable hosting, as well as features like automatic SSL certificates and custom domains. It also has a generous free tier that includes up to 100GB of bandwidth and 1,000 build minutes per month. However, it can be more expensive than some other options for high-traffic websites.&lt;/p&gt;

&lt;p&gt;✓ Git integration&lt;br&gt;
✓ Free plan&lt;/p&gt;

&lt;h2&gt;
  
  
  Vercel (Spinal's 2023 runner-up)
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---ZNZ6g_x--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7838kt2igle7cfw1ds31.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---ZNZ6g_x--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7838kt2igle7cfw1ds31.jpg" alt="Vercel logo" width="300" height="100"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://vercel.com/"&gt;Vercel&lt;/a&gt; is a hosting and deployment platform specifically designed for static and Jamstack (JavaScript, APIs, and Markup) applications. It offers fast, reliable hosting, as well as features like custom domains and automatic SSL certificates. It also has a generous free tier that includes up to 100GB of bandwidth and 2,000 build minutes per month. But Vervel too, may not be the most cost-effective option for high-traffic websites.&lt;/p&gt;

&lt;p&gt;✓ Git integration&lt;br&gt;
✓ Free plan&lt;/p&gt;

&lt;h2&gt;
  
  
  GitHub Pages
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ITckubrT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/osrptdid0e6yyj6irooq.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ITckubrT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/osrptdid0e6yyj6irooq.jpg" alt="Github logo" width="300" height="100"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://pages.github.com/"&gt;GitHub Pages&lt;/a&gt; is a free hosting service provided by GitHub that allows you to host a static website directly from a GitHub repository. It's easy to use, and there are no additional fees or hosting costs. As it does not offer advanced features like custom domains or SSL certificates, and it's not suitable for websites with high traffic.&lt;/p&gt;

&lt;p&gt;✓ Git integration (built-in, duh!)&lt;br&gt;
✓ Free plan&lt;/p&gt;

&lt;h2&gt;
  
  
  Render
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--t01_FIbj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3bid1odktt7c01di7aqm.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--t01_FIbj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3bid1odktt7c01di7aqm.jpg" alt="Render logo" width="300" height="100"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://render.com/"&gt;Render&lt;/a&gt; is a hosting platform specifically designed for static and Jamstack applications. It offers fast, reliable hosting, as well as features like automatic SSL certificates and custom domains. It also has a range of pricing plans to suit different needs, including a free tier. However, it may not be the most cost-effective option for high-traffic websites.&lt;/p&gt;

&lt;p&gt;✓ Git integration&lt;br&gt;
✓ Free plan&lt;/p&gt;

&lt;h2&gt;
  
  
  Cloudflare
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rjL-EJgA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t6792kpn17v0x36c76xq.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rjL-EJgA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t6792kpn17v0x36c76xq.jpg" alt="Cloudflare logo" width="300" height="100"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://pages.cloudflare.com/"&gt;Cloudflare&lt;/a&gt; is a cloud-based service that provides security and performance optimization for websites. It can be used to host static websites, and it offers features like custom domains, SSL certificates, and a global content delivery network. It also has a range of pricing plans to suit different needs, with a free tier that includes up to 100GB of bandwidth per month. Also Cloudflare may not be the most cost-effective option for high-traffic websites.&lt;/p&gt;

&lt;p&gt;x Git integration&lt;br&gt;
✓ Free plan&lt;/p&gt;

&lt;h2&gt;
  
  
  Surge
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--PHehj6h8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rxz7ojaldz57mc9xij4k.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--PHehj6h8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rxz7ojaldz57mc9xij4k.jpg" alt="Surge logo" width="300" height="100"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://surge.sh/"&gt;Surge&lt;/a&gt; is a hosting platform specifically designed for static websites. It offers fast, reliable hosting, as well as features like custom domains and automatic SSL certificates. It has a simple pricing model, with a single paid plan that includes unlimited bandwidth and a custom domain. It also has a free tier that can get you pretty far.&lt;/p&gt;

&lt;p&gt;✓ Git integration (via Git Hooks)&lt;br&gt;
✓ Free plan&lt;/p&gt;

&lt;h2&gt;
  
  
  DigitalOcean Spaces
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--WFriry4L--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t749yus3owmbwfl3cjn5.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--WFriry4L--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t749yus3owmbwfl3cjn5.jpg" alt="DO logo" width="300" height="100"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.digitalocean.com/products/spaces"&gt;DigitalOcean Spaces&lt;/a&gt; is a low-cost, easy-to-use option for hosting a static website. It offers features like custom domains and SSL certificates, and it's highly scalable and reliable. However, it may not be the best choice for high-traffic websites, as it can be more expensive than some other options.&lt;/p&gt;

&lt;p&gt;x Git integration&lt;br&gt;
x No free plan&lt;/p&gt;

&lt;h2&gt;
  
  
  AWS S3
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3yw7CO0u--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/05ozgzkz9zzy52d6emew.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3yw7CO0u--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/05ozgzkz9zzy52d6emew.jpg" alt="AWS S3 logo" width="300" height="100"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/s3/"&gt;Amazon S3 (Simple Storage Service)&lt;/a&gt; is an object storage service that can be used to host static websites. It's highly scalable and reliable, but it can be, too, more expensive than other options, especially for websites with high traffic. It's also not the easiest service to use, as it requires some technical knowledge to set up and manage.&lt;/p&gt;

&lt;p&gt;x Git integration&lt;br&gt;
x No free plan&lt;/p&gt;

&lt;h2&gt;
  
  
  Azure Storage
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CJoEmNBT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ra35juhc91vwuvrvcdn1.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CJoEmNBT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ra35juhc91vwuvrvcdn1.jpg" alt="azure logo" width="300" height="100"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://azure.microsoft.com/en-us/products/category/storage/"&gt;Microsoft Azure Storage&lt;/a&gt; is another option for hosting a static website, with a range of pricing plans to suit different needs. It offers features like custom domains and SSL certificates, and it's highly scalable and reliable. Also Azure Storage can be more expensive than some other options, especially for high-traffic websites.&lt;/p&gt;

&lt;p&gt;x Git integration&lt;br&gt;
x No free plan&lt;/p&gt;

&lt;h2&gt;
  
  
  Google Cloud Storage
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--IgERIJJv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ti104eifirqgywr3wcw8.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--IgERIJJv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ti104eifirqgywr3wcw8.jpg" alt="GCP logo" width="300" height="100"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cloud.google.com/storage/"&gt;Google Cloud Storage&lt;/a&gt; is a flexible, scalable, and cost-effective option for hosting a static website. It offers features like custom domains and SSL certificates, and it's easy to use. However, it can be more expensive than some other options for high-traffic websites.&lt;/p&gt;

&lt;p&gt;x Git integration&lt;br&gt;
x No free plan&lt;/p&gt;

&lt;h2&gt;
  
  
  Other lesser-known options
&lt;/h2&gt;

&lt;p&gt;Here are a few additional options that might be worth considering:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.hostinger.com/"&gt;Hostinger&lt;/a&gt; is a web hosting provider that offers a range of hosting options, including static website hosting. It has a range of pricing plans to suit different needs, with a free tier that includes up to 10GB of storage and 100GB of bandwidth per month.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.a2hosting.com/"&gt;A2 Hosting&lt;/a&gt; is a web hosting provider that offers static website hosting as well as other hosting options. It has a range of pricing plans to suit different needs, and it offers features like custom domains and SSL certificates.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.siteground.com/"&gt;SiteGround&lt;/a&gt; is a web hosting provider that offers a range of hosting options, including static website hosting. It has a range of pricing plans to suit different needs, and it offers features like custom domains and SSL certificates.&lt;/p&gt;

&lt;h2&gt;
  
  
  How about managing your own server?
&lt;/h2&gt;

&lt;p&gt;Setting up your own server to host a static website can be a complex and time-consuming process. It involves selecting and purchasing a server, installing an operating system, configuring the server, setting up a continous deploy system, and managing and maintaining the server. This can be difficult and requires technical expertise, as well as ongoing maintenance and management. Using one of the providers listed above can be a more practical option, as they take care of these tasks. So you can focus on building your static marketing site.&lt;/p&gt;

&lt;p&gt;Overall, the best choice for hosting your static website will depend on your specific needs and budget. Most offer really generous free plans, but can get pretty expensive if you get, let's say, the (orange) &lt;a href="https://www.urbandictionary.com/define.php?term=hug%20of%20death"&gt;hug of death&lt;/a&gt;.&lt;br&gt;
Consider the features that are important to you, as well as the cost and scalability of the hosting provider, to make the best decision for your website.&lt;/p&gt;

&lt;p&gt;Are there any options missing or is something wrong? &lt;a href="//mailto:support@spinalcms.com"&gt;Let me know&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ssg</category>
      <category>host</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Turbo Stream responses that spark joy</title>
      <dc:creator>Eelco from Spinal</dc:creator>
      <pubDate>Thu, 15 Dec 2022 10:27:45 +0000</pubDate>
      <link>https://dev.to/spinal/turbo-stream-responses-that-spark-joy-25mj</link>
      <guid>https://dev.to/spinal/turbo-stream-responses-that-spark-joy-25mj</guid>
      <description>&lt;p&gt;&lt;a href="https://turbo.hotwired.dev/handbook/streams" rel="noopener noreferrer"&gt;Turbo Streams&lt;/a&gt;, either fired via a controller action or over websockets are an incredibly easy but powerful way to make your apps more responsive and interactive. &lt;/p&gt;

&lt;p&gt;At &lt;a href="https://spinalcms.com/" rel="noopener noreferrer"&gt;Spinal&lt;/a&gt;, the micro-SaaS I've founded I'm all-in on this “boring Rails stack”. It helps me ship features quickly without a lot of overhead. &lt;/p&gt;

&lt;p&gt;There is one technique that I haven't seen talked about before in the Rails/Hotwire sphere, but I use it often to spark joy when using my SaaS app: &lt;strong&gt;add an animation when inserting elements into the DOM via Turbo Streams&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;It's simple, really. Have an optional &lt;code&gt;animated: false&lt;/code&gt; attribute on the ViewComponent or the partial that you insert. Let's see some of these in action and then go over each step needed to replicate this.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fade in a new icon
&lt;/h2&gt;

&lt;p&gt;Customers can change the icon for their content type. Upon save, the icon is shown on the page with a subtle fade- and zoom-in animation. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fomv7wlhmv7yfu7tnj07k.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fomv7wlhmv7yfu7tnj07k.gif" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Animate content rows
&lt;/h2&gt;

&lt;p&gt;When changing the order of the content, each row is fading in one row at a time. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxkfy56hnn5xaizskqquw.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxkfy56hnn5xaizskqquw.gif" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Animate content cells individually
&lt;/h2&gt;

&lt;p&gt;When customising which columns are visible in the content overview, they are shown top to bottom and from left to right with a fade-in animation. Notice how they are not fading in all at once, but one at the time. Sparking all the more joy. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flvfpxn3ixzrla19ugotf.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flvfpxn3ixzrla19ugotf.gif" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's important to note that these animations do not get triggered on regular page visits—which would get annoying pretty quickly, but only when triggered via Turbo Streams. So how do you this?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Feel free to &lt;a href="https://spinalcms.com/" rel="noopener noreferrer"&gt;sign up for Spinal&lt;/a&gt; to see these little animations in 4K instead of a crappy gif.&lt;/em&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  How to spark joy yourself
&lt;/h2&gt;

&lt;p&gt;Let's walk through all the critical parts on how to do this. I'm using &lt;a href="https://github.com/viewcomponent/view_component" rel="noopener noreferrer"&gt;ViewComponent&lt;/a&gt;, but the same principles apply if you use Rails' partials.&lt;/p&gt;

&lt;h3&gt;
  
  
  ViewComponent class
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ContentRowComponent&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationComponent&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content_row&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="n"&gt;content_row_counter&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="ss"&gt;animate: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@content_row&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;content_row&lt;/span&gt;
    &lt;span class="vi"&gt;@animate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;animate&lt;/span&gt;
    &lt;span class="vi"&gt;@content_row_counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;content_row_counter&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# … other methods omitted for brevity&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;row_css&lt;/span&gt;
    &lt;span class="n"&gt;class_names&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;"content-row"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"animate-fadeIn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="vi"&gt;@animate&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First initialise the &lt;code&gt;@content_row_counter&lt;/code&gt; and the &lt;code&gt;@animate&lt;/code&gt; instance variable (set it to &lt;code&gt;false&lt;/code&gt; by default). The &lt;code&gt;content_row_counter&lt;/code&gt; is a variable coming from &lt;a href="https://viewcomponent.org/guide/collections.html#collection-counter" rel="noopener noreferrer"&gt;ViewComponent&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Then a &lt;code&gt;row_css&lt;/code&gt; method where all the css for the &lt;code&gt;&amp;lt;li /&amp;gt;&lt;/code&gt;-element is defined. Here the most interesting is the &lt;code&gt;class_names()&lt;/code&gt; helper from &lt;a href="https://api.rubyonrails.org/classes/ActionView/Helpers/TagHelper.html#method-i-token_list" rel="noopener noreferrer"&gt;ActionView&lt;/a&gt;. It only appends the &lt;code&gt;animate-fadeIn&lt;/code&gt; class if &lt;code&gt;@animate&lt;/code&gt; is &lt;code&gt;true&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  ViewComponent erb
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;%= tag.li @content_row, class: row_css, style: "animation-delay: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vi"&gt;@content_row_counter&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sx"&gt;ms;" %&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;row_css&lt;/code&gt; is referenced here. Also a delay for the animation is set which simply is &lt;code&gt;@content_row_counter&lt;/code&gt; multiplied by 70(ms). This then makes each row fade in one after another. &lt;/p&gt;

&lt;h3&gt;
  
  
  Turbo Stream response
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;%= turbo_stream.update "content-rows" do %&amp;gt;
  &amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="no"&gt;ContentRowComponent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with_collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;animate: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;% end &lt;/span&gt;&lt;span class="o"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And finally, where the magic happens. Upon saving, this turbo_stream method is called. It updates the element with the id of &lt;code&gt;content-rows&lt;/code&gt;. And only here is &lt;code&gt;animate&lt;/code&gt; set to &lt;code&gt;true&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;You are, of course, not limited to a boolean &lt;code&gt;animate&lt;/code&gt; variable. You could as well pass the animation name as a string and like that add more advanced animation option based on your needs. For example: &lt;code&gt;animate: fadeInSlow&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;And that's the bare essentials of sparking joy to your Turbo Streams. If you do apply this technique, do share the results with. Love to see it.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>hotwire</category>
      <category>turbo</category>
      <category>ux</category>
    </item>
  </channel>
</rss>
