<?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: Konstantin Kai</title>
    <description>The latest articles on DEV Community by Konstantin Kai (@konstantinkai).</description>
    <link>https://dev.to/konstantinkai</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%2F102922%2F40dc4f77-ae62-41a7-b6d2-283fd6f0d91c.jpeg</url>
      <title>DEV Community: Konstantin Kai</title>
      <link>https://dev.to/konstantinkai</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/konstantinkai"/>
    <language>en</language>
    <item>
      <title>ReelKit: A Virtualized TikTok-Style Slider Engine</title>
      <dc:creator>Konstantin Kai</dc:creator>
      <pubDate>Mon, 16 Mar 2026 10:58:23 +0000</pubDate>
      <link>https://dev.to/konstantinkai/reelkit-a-virtualized-tiktok-style-slider-engine-4h44</link>
      <guid>https://dev.to/konstantinkai/reelkit-a-virtualized-tiktok-style-slider-engine-4h44</guid>
      <description>&lt;p&gt;You're building a vertical feed — TikTok-style swipe, full-screen slides, thousands of items. You reach for a carousel library and hit the wall: it renders all slides to the DOM, chokes on touch gestures, and bundles half the internet.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://reelkit.dev" rel="noopener noreferrer"&gt;ReelKit&lt;/a&gt; renders &lt;strong&gt;3 DOM nodes&lt;/strong&gt; at any time — previous, current, next — whether you have 4 slides or 40,000. Zero dependencies in core. Touch-first with momentum and snap. ~3.7 kB gzipped.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://stackblitz.com/github/KonstantinKai/reelkit-react-starter" rel="noopener noreferrer"&gt;Try it live on StackBlitz&lt;/a&gt;&lt;/strong&gt; — no setup needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick start
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @reelkit/react
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&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;useState&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;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Reel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ReelIndicator&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;@reelkit/react&lt;/span&gt;&lt;span class="dl"&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;slides&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Discover&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#6366f1&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="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Trending&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#8b5cf6&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="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Following&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#ec4899&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="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;For You&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#14b8a6&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;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setIndex&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Reel&lt;/span&gt;
      &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;slides&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;100%&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;100dvh&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"vertical"&lt;/span&gt;
      &lt;span class="na"&gt;enableWheel&lt;/span&gt;
      &lt;span class="na"&gt;useNavKeys&lt;/span&gt;
      &lt;span class="na"&gt;afterChange&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;setIndex&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;itemBuilder&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_inRange&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;size&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;
          &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="na"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;slides&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;flex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;alignItems&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;center&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;justifyContent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;center&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;white&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;fontSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2rem&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="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;slides&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ReelIndicator&lt;/span&gt; &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;slides&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;active&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Reel&lt;/span&gt;&lt;span class="p"&gt;&amp;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;Swipe, keyboard arrows, mouse wheel — all work out of the box. The &lt;code&gt;size&lt;/code&gt; prop is optional: omit it and ReelKit auto-measures via &lt;code&gt;ResizeObserver&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Programmatic navigation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apiRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ReelApi&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Reel&lt;/span&gt; &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;apiRef&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;apiRef&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;itemBuilder&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&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;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Slide&lt;/span&gt; &lt;span class="na"&gt;index&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;

&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;apiRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Prev&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;apiRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Next&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;apiRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;goTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Jump to 50&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Navigation methods return promises — &lt;code&gt;await apiRef.current!.next()&lt;/code&gt; resolves when the animation completes, so you can chain transitions sequentially.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Virtualization
&lt;/h3&gt;

&lt;p&gt;Only render what's visible. ReelKit computes a visible range from the current index:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Current index: 50, count: 10,000
Visible: [49, 50, 51]  ← 3 DOM nodes, always
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With loop mode, it wraps at boundaries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Current index: 0, loop: true
Visible: [9999, 0, 1]  ← seamless wrap
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you call &lt;code&gt;goTo(5000)&lt;/code&gt; from index 0, it doesn't animate through 5,000 slides. It temporarily swaps the adjacent slide with the target, animates a single step, and resolves. One smooth transition — the virtualization handles the rest.&lt;/p&gt;

&lt;h3&gt;
  
  
  Signals, not React state
&lt;/h3&gt;

&lt;p&gt;ReelKit implements its own reactive system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Signal&lt;/strong&gt; — writable observable value&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ComputedSignal&lt;/strong&gt; — lazy derived value (zero cost when unobserved)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;batch()&lt;/strong&gt; — groups multiple updates into a single notification pass&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The React &lt;code&gt;&amp;lt;Reel&amp;gt;&lt;/code&gt; component subscribes to these signals in effects and uses &lt;code&gt;flushSync()&lt;/code&gt; on each animation frame to apply transforms synchronously — bypassing React's default batching. Your React tree doesn't re-render during swipes — transforms update at 60fps without touching component state.&lt;/p&gt;

&lt;p&gt;When an animation completes, the index and transform value update in a single &lt;code&gt;batch()&lt;/code&gt; call — observers never see an intermediate state. Navigation methods (&lt;code&gt;next()&lt;/code&gt;, &lt;code&gt;goTo()&lt;/code&gt;) return promises that resolve on completion, so you can chain transitions or await before taking the next action.&lt;/p&gt;

&lt;h3&gt;
  
  
  Touch-first gestures
&lt;/h3&gt;

&lt;p&gt;The gesture controller detects the dominant axis from the initial touch vector and locks to it. It tracks per-frame delta, cumulative distance, and velocity. A fast swipe (&amp;gt; 1400 px/s) or drag past the threshold triggers a slide change with snap-back animation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Packages
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Package&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;Size (gzip)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@reelkit/core&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Framework-agnostic engine&lt;/td&gt;
&lt;td&gt;3.7 kB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@reelkit/react&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;React components + hooks&lt;/td&gt;
&lt;td&gt;2.6 kB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@reelkit/react-reel-player&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Full-screen video reel player&lt;/td&gt;
&lt;td&gt;3.8 kB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@reelkit/react-lightbox&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Image &amp;amp; video gallery lightbox&lt;/td&gt;
&lt;td&gt;3.4 kB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;@reelkit/core&lt;/strong&gt; is the engine — all slider logic, gesture detection, keyboard/wheel controllers, and the signal system. Zero dependencies. Framework-agnostic. Vue bindings are in progress.&lt;/p&gt;

&lt;p&gt;Everything in core is factory functions, not classes — &lt;code&gt;createSliderController&lt;/code&gt;, &lt;code&gt;createGestureController&lt;/code&gt;, &lt;code&gt;createKeyboardController&lt;/code&gt;, &lt;code&gt;createWheelController&lt;/code&gt;. Plain closures, no &lt;code&gt;this&lt;/code&gt; binding issues, better tree-shaking.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;@reelkit/react&lt;/strong&gt; bridges the core to React. The &lt;code&gt;&amp;lt;Reel&amp;gt;&lt;/code&gt; component creates a &lt;code&gt;SliderController&lt;/code&gt; once via &lt;code&gt;useState&lt;/code&gt; initializer and never recreates it. &lt;code&gt;&amp;lt;ReelIndicator&amp;gt;&lt;/code&gt; renders Instagram-style scrollable dot indicators.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reel Player
&lt;/h3&gt;

&lt;p&gt;A ready-made TikTok/Instagram Reels overlay:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&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;ReelPlayerOverlay&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;@reelkit/react-reel-player&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@reelkit/react-reel-player/styles.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ReelPlayerOverlay&lt;/span&gt;
  &lt;span class="na"&gt;isOpen&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isOpen&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;onClose&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setIsOpen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;initialIndex&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Videos autoplay when the slide becomes active, pause when swiped away. A shared video element is reused across slides for iOS sound continuity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lightbox
&lt;/h3&gt;

&lt;p&gt;Full-screen image gallery with three transition modes (slide, fade, zoom-in), swipe-to-close, keyboard navigation, and fullscreen API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&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;LightboxOverlay&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;@reelkit/react-lightbox&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@reelkit/react-lightbox/styles.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;LightboxOverlay&lt;/span&gt;
  &lt;span class="na"&gt;isOpen&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;images&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;images&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;initialIndex&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;onClose&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"fade"&lt;/span&gt;
&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Video support is opt-in and tree-shakeable — image-only usage pays zero extra cost.&lt;/p&gt;

&lt;p&gt;Both packages expose render props for controls, navigation, and slide content — replace anything you need.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://reelkit.dev" rel="noopener noreferrer"&gt;Documentation &amp;amp; demos&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/KonstantinKai/reelkit" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/package/@reelkit/core" rel="noopener noreferrer"&gt;npm: @reelkit/core&lt;/a&gt; | &lt;a href="https://www.npmjs.com/package/@reelkit/react" rel="noopener noreferrer"&gt;@reelkit/react&lt;/a&gt; | &lt;a href="https://www.npmjs.com/package/@reelkit/react-reel-player" rel="noopener noreferrer"&gt;@reelkit/react-reel-player&lt;/a&gt; | &lt;a href="https://www.npmjs.com/package/@reelkit/react-lightbox" rel="noopener noreferrer"&gt;@reelkit/react-lightbox&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://stackblitz.com/github/KonstantinKai/reelkit-react-starter" rel="noopener noreferrer"&gt;StackBlitz starter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're building a vertical feed, a reel player, or a gallery lightbox in React — give ReelKit a try. MIT licensed, open source.&lt;/p&gt;

&lt;p&gt;Feedback, suggestions, and bug reports are welcome — &lt;a href="https://github.com/KonstantinKai/reelkit/issues" rel="noopener noreferrer"&gt;open an issue&lt;/a&gt; or drop a comment below. And if ReelKit saved you some time, a &lt;a href="https://github.com/KonstantinKai/reelkit" rel="noopener noreferrer"&gt;GitHub star&lt;/a&gt; would mean a lot — it's a small thing, but it really helps the project get noticed.&lt;/p&gt;

</description>
      <category>react</category>
      <category>typescript</category>
      <category>opensource</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Managing Flutter &amp; Dart SDK Versions with Proto</title>
      <dc:creator>Konstantin Kai</dc:creator>
      <pubDate>Mon, 09 Mar 2026 16:03:31 +0000</pubDate>
      <link>https://dev.to/konstantinkai/managing-flutter-dart-sdk-versions-with-proto-1m5j</link>
      <guid>https://dev.to/konstantinkai/managing-flutter-dart-sdk-versions-with-proto-1m5j</guid>
      <description>&lt;p&gt;"It works on my machine." Three Flutter projects, three different SDK versions, and &lt;code&gt;flutter downgrade&lt;/code&gt; is your most-used command. There has to be a better way — and there is.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://moonrepo.dev/proto" rel="noopener noreferrer"&gt;Proto&lt;/a&gt; is a universal version manager (think &lt;code&gt;nvm&lt;/code&gt; or &lt;code&gt;asdf&lt;/code&gt;, but faster and cross-platform) — and now it supports Flutter and Dart through community WASM plugins.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why proto over FVM or asdf?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;FVM&lt;/strong&gt; is Flutter-specific. It works, but it's one more tool in your stack. If you already manage Node, Go, Rust, or Python versions, that's yet another version manager with its own config format.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;asdf&lt;/strong&gt; supports many tools via plugins, but it's historically been shell-script based, Unix-only, and can be slow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;proto&lt;/strong&gt; gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One tool for all your SDKs (Node, Go, Rust, Python, Flutter, Dart, PHP, and more)&lt;/li&gt;
&lt;li&gt;Blazing fast — plugins are compiled to WASM, not shell scripts&lt;/li&gt;
&lt;li&gt;Cross-platform — Linux, macOS, and Windows&lt;/li&gt;
&lt;li&gt;Per-project version pinning via &lt;code&gt;.prototools&lt;/code&gt; (one file, all tools)&lt;/li&gt;
&lt;li&gt;Automatic version detection from &lt;code&gt;pubspec.yaml&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Auto-switching — proto picks the right version when you &lt;code&gt;cd&lt;/code&gt; into a project&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Install proto
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# macOS / Linux&lt;/span&gt;
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://moonrepo.dev/install/proto.sh | bash

&lt;span class="c"&gt;# Windows&lt;/span&gt;
irm https://moonrepo.dev/install/proto.ps1 | iex
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Add the Flutter plugin
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;proto plugin add flutter &lt;span class="s2"&gt;"github://KonstantinKai/proto-flutter-plugin"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One command. No git clones, no compiling from source. Proto downloads a tiny WASM plugin and you're ready to go.&lt;/p&gt;

&lt;h3&gt;
  
  
  Install Flutter
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install the latest stable&lt;/span&gt;
proto &lt;span class="nb"&gt;install &lt;/span&gt;flutter

&lt;span class="c"&gt;# Install a specific version&lt;/span&gt;
proto &lt;span class="nb"&gt;install &lt;/span&gt;flutter 3.29

&lt;span class="c"&gt;# Install a beta version&lt;/span&gt;
proto &lt;span class="nb"&gt;install &lt;/span&gt;flutter beta
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Proto downloads the official Flutter SDK archive from Google's servers, extracts it, and makes it available immediately.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pin a version per project
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;my-flutter-project
proto pin flutter 3.29
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates (or updates) a &lt;code&gt;.prototools&lt;/code&gt; file in your project root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="py"&gt;flutter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"3.29"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now every teammate gets the exact same Flutter version on &lt;code&gt;proto install&lt;/code&gt;. No surprises.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automatic version detection
&lt;/h2&gt;

&lt;p&gt;The plugin reads your &lt;code&gt;pubspec.yaml&lt;/code&gt; out of the box. If you have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;flutter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;=3.22.0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;lt;4.0.0"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Proto will detect and resolve the appropriate Flutter version — no extra configuration needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Managing Dart alongside Flutter
&lt;/h2&gt;

&lt;p&gt;Flutter bundles Dart, so in most cases you don't need a separate Dart plugin. But if you have pure Dart projects (CLI tools, server apps, packages), there's a &lt;a href="https://github.com/KonstantinKai/proto-dart-plugin" rel="noopener noreferrer"&gt;Dart plugin&lt;/a&gt; too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;proto plugin add dart &lt;span class="s2"&gt;"github://KonstantinKai/proto-dart-plugin"&lt;/span&gt;
proto &lt;span class="nb"&gt;install &lt;/span&gt;dart 3.7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It reads &lt;code&gt;environment.sdk&lt;/code&gt; from &lt;code&gt;pubspec.yaml&lt;/code&gt; and supports the same version pinning and auto-detection workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  One config file for your entire stack
&lt;/h2&gt;

&lt;p&gt;Here's where proto really shines. A single &lt;code&gt;.prototools&lt;/code&gt; file manages everything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="py"&gt;flutter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"3.29"&lt;/span&gt;
&lt;span class="py"&gt;node&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"22"&lt;/span&gt;
&lt;span class="py"&gt;go&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1.24"&lt;/span&gt;

&lt;span class="nn"&gt;[plugins.tools]&lt;/span&gt;
&lt;span class="py"&gt;flutter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"github://KonstantinKai/proto-flutter-plugin"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your whole team gets consistent SDK versions across Flutter, Dart, Node, Go — whatever your project needs. One file, committed to git, no ambiguity.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works under the hood
&lt;/h2&gt;

&lt;p&gt;Curious about the internals?&lt;/p&gt;

&lt;p&gt;Proto plugins are compiled to WebAssembly (WASM) and run in a sandboxed environment. The Flutter plugin:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Fetches the official Flutter release manifest from Google's servers&lt;/li&gt;
&lt;li&gt;Filters versions by your OS and architecture&lt;/li&gt;
&lt;li&gt;Downloads and verifies the SDK archive (with SHA-256 checksum)&lt;/li&gt;
&lt;li&gt;Extracts it to proto's tool directory&lt;/li&gt;
&lt;li&gt;Exposes both &lt;code&gt;flutter&lt;/code&gt; and &lt;code&gt;dart&lt;/code&gt; executables&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There are no shell scripts, no git clones, no channel management. Just clean, deterministic version management.&lt;/p&gt;

&lt;h2&gt;
  
  
  Supported platforms
&lt;/h2&gt;

&lt;p&gt;The Flutter plugin covers all officially supported platforms:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Platform&lt;/th&gt;
&lt;th&gt;Architecture&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Linux&lt;/td&gt;
&lt;td&gt;x64&lt;/td&gt;
&lt;td&gt;All versions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;macOS&lt;/td&gt;
&lt;td&gt;x64&lt;/td&gt;
&lt;td&gt;All versions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;macOS&lt;/td&gt;
&lt;td&gt;arm64 (Apple Silicon)&lt;/td&gt;
&lt;td&gt;Stable &amp;gt;= 3.0.0, beta &amp;gt;= 2.12.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Windows&lt;/td&gt;
&lt;td&gt;x64&lt;/td&gt;
&lt;td&gt;All versions&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Useful commands
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# List all available Flutter versions&lt;/span&gt;
proto versions flutter

&lt;span class="c"&gt;# Check which version is active&lt;/span&gt;
proto run flutter &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;--version&lt;/span&gt;

&lt;span class="c"&gt;# Switch between versions&lt;/span&gt;
proto &lt;span class="nb"&gt;install &lt;/span&gt;flutter 3.22
proto pin flutter 3.22

&lt;span class="c"&gt;# Remove a version&lt;/span&gt;
proto uninstall flutter 3.22
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;p&gt;The plugins are open source and available on GitHub:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/KonstantinKai/proto-flutter-plugin" rel="noopener noreferrer"&gt;proto-flutter-plugin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/KonstantinKai/proto-dart-plugin" rel="noopener noreferrer"&gt;proto-dart-plugin&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you hit any issues or have feature requests, open an issue on GitHub.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you also work with PHP, check out &lt;a href="https://github.com/KonstantinKai/proto-php-plugin" rel="noopener noreferrer"&gt;proto-php-plugin&lt;/a&gt; and &lt;a href="https://github.com/KonstantinKai/proto-composer-plugin" rel="noopener noreferrer"&gt;proto-composer-plugin&lt;/a&gt; — same approach, same workflow.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>dart</category>
      <category>tooling</category>
      <category>devops</category>
    </item>
    <item>
      <title>Stop Building API Dashboards From Scratch</title>
      <dc:creator>Konstantin Kai</dc:creator>
      <pubDate>Thu, 05 Mar 2026 11:35:47 +0000</pubDate>
      <link>https://dev.to/konstantinkai/stop-building-api-dashboards-from-scratch-4deo</link>
      <guid>https://dev.to/konstantinkai/stop-building-api-dashboards-from-scratch-4deo</guid>
      <description>&lt;p&gt;Every API developer has been there. You ship an API, someone starts using it, and the questions begin:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"How many requests are we getting?"&lt;/li&gt;
&lt;li&gt;"Who's our heaviest consumer?"&lt;/li&gt;
&lt;li&gt;"Why did error rates spike at 3am?"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So you write a few SQL queries. Maybe stand up a Grafana instance. Add some log parsing. Before you know it, you've spent two days on infrastructure that has nothing to do with your actual product.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern I kept repeating
&lt;/h2&gt;

&lt;p&gt;For every API project, I built some version of the same system:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Log request metadata to a database&lt;/li&gt;
&lt;li&gt;Write queries to aggregate it&lt;/li&gt;
&lt;li&gt;Build a dashboard to visualize it&lt;/li&gt;
&lt;li&gt;Set up alerts when things go wrong&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The fourth time I did this, I realized it should be a product.&lt;/p&gt;

&lt;h2&gt;
  
  
  PeekAPI: one middleware call, full API analytics
&lt;/h2&gt;

&lt;p&gt;PeekAPI is a middleware you add to your API server. Here's the full setup:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Node.js (Express):&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;peekapi&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="s2"&gt;@peekapi/sdk-node&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;peekapi&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pk_...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Python (FastAPI):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;peekapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;PeekAPIMiddleware&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_middleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PeekAPIMiddleware&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pk_...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Go (net/http):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;peekapi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Middleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mux&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;peekapi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;APIKey&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"pk_..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListenAndServe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;":8080"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Rust (Actix Web):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nn"&gt;App&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;.wrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;PeekApi&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"pk_..."&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nf"&gt;.service&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="cm"&gt;/* your routes */&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Ruby (Rails):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/application.rb&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt; &lt;span class="no"&gt;PeekAPI&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Middleware&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;api_key: &lt;/span&gt;&lt;span class="s2"&gt;"pk_..."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;PHP (Laravel):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// bootstrap/app.php&lt;/span&gt;
&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;withMiddleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Middleware&lt;/span&gt; &lt;span class="nv"&gt;$middleware&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$middleware&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;\PeekAPI\Laravel\PeekApiMiddleware&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&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;&lt;strong&gt;Java (Spring Boot):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="c"&gt;# application.properties
&lt;/span&gt;&lt;span class="py"&gt;peekapi.api-key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;pk_...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the entire integration for each language. No agents, no config files, no infrastructure to manage.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you get
&lt;/h2&gt;

&lt;p&gt;Once the middleware is running, your dashboard shows:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Real-time request stream&lt;/strong&gt; — every API call as it happens, with method, path, status, latency, and consumer identity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Endpoint analytics&lt;/strong&gt; — request volume, error rates, and average latency for each route. Spot which endpoints are most used, which are failing, and which are slow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Consumer tracking&lt;/strong&gt; — PeekAPI automatically identifies who's calling your API from authorization headers or API keys. Consumers are identified by a SHA-256 hash — raw credentials never leave your server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Smart alerts&lt;/strong&gt; — get notified when error rates spike, latency exceeds thresholds, or an endpoint goes silent. Notifications via email, Slack, Discord, Telegram, or generic webhook.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it captures (and what it doesn't)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Captures:&lt;/strong&gt; HTTP method, path, status code, response time, request/response size, and a hashed consumer identifier.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does NOT capture:&lt;/strong&gt; request/response bodies, query parameters, or raw authentication credentials.&lt;/p&gt;

&lt;p&gt;This is a deliberate design choice. PeekAPI answers "who, what, when, how fast" — not "what data was in the request." If you need payload inspection, you need a different tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  Zero dependencies, by design
&lt;/h2&gt;

&lt;p&gt;Every SDK is zero-dependency. The Node SDK uses only built-in modules (&lt;code&gt;https&lt;/code&gt;, &lt;code&gt;crypto&lt;/code&gt;, &lt;code&gt;fs&lt;/code&gt;, &lt;code&gt;os&lt;/code&gt;). Python uses only stdlib. Same for Go, Rust, Ruby, PHP, and Java.&lt;/p&gt;

&lt;p&gt;Why? Two reasons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;No supply chain risk.&lt;/strong&gt; Your API middleware shouldn't pull in a tree of transitive dependencies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No conflicts.&lt;/strong&gt; The SDK will never clash with your existing dependency versions.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Built for reliability
&lt;/h2&gt;

&lt;p&gt;The SDKs are designed to never affect your API's performance or reliability:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Async buffering&lt;/strong&gt; — events are collected in memory and flushed in batches (configurable interval and batch size)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exponential backoff&lt;/strong&gt; — if the analytics server is down, the SDK backs off automatically (max 5 consecutive failures)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Disk persistence&lt;/strong&gt; — after max flush failures, on non-retryable errors, or on process shutdown, undelivered events are saved to a JSONL file. Recovered automatically every 60 seconds and on startup&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Graceful shutdown&lt;/strong&gt; — SIGTERM/SIGINT handlers persist buffered events to disk, recovered automatically on restart&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If PeekAPI's servers are unreachable, your API keeps running normally. Analytics are best-effort — they should never be a single point of failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  7 SDKs, 18+ frameworks
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Language&lt;/th&gt;
&lt;th&gt;Frameworks&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Node.js&lt;/td&gt;
&lt;td&gt;Express, Fastify, Koa, Hapi, NestJS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;td&gt;ASGI, WSGI, Django&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Go&lt;/td&gt;
&lt;td&gt;net/http, Gin, Echo, Fiber, Chi&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rust&lt;/td&gt;
&lt;td&gt;Actix Web, Axum, Rocket&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ruby&lt;/td&gt;
&lt;td&gt;Rack, Rails&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PHP&lt;/td&gt;
&lt;td&gt;PSR-15, Laravel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Java&lt;/td&gt;
&lt;td&gt;Spring Boot, Jakarta Servlet&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;Free tier at &lt;a href="https://peekapi.dev" rel="noopener noreferrer"&gt;peekapi.dev&lt;/a&gt; — 500K events/month, no credit card required. SDKs are MIT licensed.&lt;/p&gt;

&lt;p&gt;If you have questions about the architecture or feature requests, I'd love to hear them in the comments.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>api</category>
      <category>opensource</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
