<?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: Isfaaq M. F. Emambocus</title>
    <description>The latest articles on DEV Community by Isfaaq M. F. Emambocus (@imfemambocus).</description>
    <link>https://dev.to/imfemambocus</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4086291%2F8e582b93-57f6-45f5-8b9a-32db5989f32e.jpeg</url>
      <title>DEV Community: Isfaaq M. F. Emambocus</title>
      <link>https://dev.to/imfemambocus</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/imfemambocus"/>
    <language>en</language>
    <item>
      <title>I used my own component library as a stranger, and it found four bugs</title>
      <dc:creator>Isfaaq M. F. Emambocus</dc:creator>
      <pubDate>Sat, 22 Aug 2026 13:52:17 +0000</pubDate>
      <link>https://dev.to/imfemambocus/i-used-my-own-component-library-as-a-stranger-and-it-found-four-bugs-13k4</link>
      <guid>https://dev.to/imfemambocus/i-used-my-own-component-library-as-a-stranger-and-it-found-four-bugs-13k4</guid>
      <description>&lt;p&gt;I write a component registry for interfaces that hold a lot of data. It has demos, it has a docs site, it has a test suite. What it did not have was a real application built on it by somebody who could not reach into the source, so I built one.&lt;/p&gt;

&lt;p&gt;The rule I set myself was that nothing could shortcut. Everything came from the published CLI against the live registry, no workspace link, no local path. If something was awkward I had to feel it rather than fix it from the inside.&lt;/p&gt;

&lt;p&gt;Four things broke.&lt;/p&gt;

&lt;p&gt;The table body had a hard height cap, so a table that is the whole page could not fill the viewport. There was no way for a caller to change it.&lt;/p&gt;

&lt;p&gt;A column head is drawn in the interface typeface and was being measured with the advance of the data typeface. In my own pairing the data face was the wider of the two, so every head fit by luck. The moment I used a narrower one, heads truncated.&lt;/p&gt;

&lt;p&gt;The chart's range selection was inserted underneath every mark. That works for a line and disappears completely under a filled bar on a continuous scale, which no demo of mine had.&lt;/p&gt;

&lt;p&gt;And Chrome draws its own focus ring on a focusable SVG element for a plain &lt;code&gt;:focus&lt;/code&gt;, where a button only gets one for &lt;code&gt;:focus-visible&lt;/code&gt;. A mouse drag on the chart left a blue outline behind that my stylesheet never asked for.&lt;/p&gt;

&lt;p&gt;All four are fixed and released. The application is a shortwave listening board, which shows what is on air right now and how much of the path between you and the transmitter is in darkness.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://grayline-sley-ui.vercel.app" rel="noopener noreferrer"&gt;https://grayline-sley-ui.vercel.app&lt;/a&gt;&lt;br&gt;
&lt;a href="https://sley-ui.dev" rel="noopener noreferrer"&gt;https://sley-ui.dev&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpcdzdhpckw94i98q1fh7.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpcdzdhpckw94i98q1fh7.gif" alt="The Grayline board, a dense dark table of shortwave transmissions. The command palette opens, Sydney is picked as the listening location, and the darkness column and the hour chart above it both redraw for the new path." width="600" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>react</category>
      <category>css</category>
      <category>opensource</category>
    </item>
    <item>
      <title>My theme switch was running 3164 animations</title>
      <dc:creator>Isfaaq M. F. Emambocus</dc:creator>
      <pubDate>Fri, 21 Aug 2026 12:41:24 +0000</pubDate>
      <link>https://dev.to/imfemambocus/my-theme-switch-was-running-3164-animations-162d</link>
      <guid>https://dev.to/imfemambocus/my-theme-switch-was-running-3164-animations-162d</guid>
      <description>&lt;p&gt;This is the first time that I'm properly getting into theme switching, so I built the dark and light mode for my component library in the most obvious way that I could find. The goal is to transition &lt;code&gt;background-color&lt;/code&gt;, &lt;code&gt;border-color&lt;/code&gt;, &lt;code&gt;color&lt;/code&gt; and &lt;code&gt;box-shadow&lt;/code&gt; on every element. But this felt a bit slow and sluggish, so I used &lt;code&gt;document.getAnimations()&lt;/code&gt; to see how many that actually runs, and the result is 3164 animations on a page of 1544 elements.&lt;/p&gt;

&lt;p&gt;After some playing around, I found out that &lt;code&gt;startViewTransition&lt;/code&gt; animates snapshots of the whole page instead, and that's 5 animations for the same job, with a gradient fading along with everything else rather than snapping. I did however find a React thing when using this function and it's that the new palette has to be in the DOM inside the callback which means the state update needs flushSync.&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="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startViewTransition&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="nf"&gt;flushSync&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;setTheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;next&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;I'm not sure this is the right way to do it, but I wrote down every number that I measured &lt;a href="https://sley-ui.dev/notes/theme-fade" rel="noopener noreferrer"&gt;https://sley-ui.dev/notes/theme-fade&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>performance</category>
      <category>webdev</category>
    </item>
    <item>
      <title>A row window makes scrolling free and does nothing for the load</title>
      <dc:creator>Isfaaq M. F. Emambocus</dc:creator>
      <pubDate>Thu, 20 Aug 2026 08:54:43 +0000</pubDate>
      <link>https://dev.to/imfemambocus/a-row-window-makes-scrolling-free-and-does-nothing-for-the-load-4mpp</link>
      <guid>https://dev.to/imfemambocus/a-row-window-makes-scrolling-free-and-does-nothing-for-the-load-4mpp</guid>
      <description>&lt;p&gt;Everyone keeps telling me to virtualize a React table past a few hundred rows. I built a row window for mine and then measured the same table twice: Once as it ships, and once with the window turned off by raising its threshold above the row count.&lt;/p&gt;

&lt;p&gt;It buys scrolling. It does nothing at all for the load, and that is the half nobody publishes.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I measured
&lt;/h2&gt;

&lt;p&gt;Compact density, ten columns, a 120Hz display, and a batch of rows loaded by a button that stands in for a fetch with a 450ms pause.&lt;/p&gt;

&lt;p&gt;Every number below comes from the production build, served locally. Never the dev server: the same 5000 row batch blocks the main thread for 1511ms through Vite's dev server and 1078.2ms from the bundle a user actually gets, hence a dev reading overstates it by about 40%.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scrolling
&lt;/h2&gt;

&lt;p&gt;At 1000 rows with every row in the DOM, the body has 12,051 cells, and a frame during a scroll sweep takes 16.7ms. A free frame on this display is 8.3ms, so the table misses one out of two and stays at 60 fps. It's fine. No one would file a bug about it.&lt;/p&gt;

&lt;p&gt;At 5000 rows it stops being fine. 60,051 cells, 78.1ms per frame, the ninetieth percentile at 81ms, the worst frame at 122.1ms. Thirteen frames a second is the difference between a table that lags and a table that has stopped answering.&lt;/p&gt;

&lt;p&gt;With the window on, both sizes put 401 cells in the body and scroll at 8.3ms. That is the display's own floor, so there is nothing left in the measurement to look at.&lt;/p&gt;

&lt;h2&gt;
  
  
  The load, which the window does not touch
&lt;/h2&gt;

&lt;p&gt;Virtualization is usually sold as the thing that makes a big list fast, so here is the other number: The longest single frame the main thread was blocked for after the click.&lt;/p&gt;

&lt;p&gt;At 1000 rows it is 183.4ms without the window and 216ms with it. At 5000 it is 1083.4ms without and 1078.2ms with. The windowed table renders 48 rows instead of 5017, and the load costs the same, or a little more.&lt;/p&gt;

&lt;p&gt;It is not the rows, and it isn't the data being built either. The click handler that makes the batch and sets the state returns in 0.6ms, over four presses between 0.5ms and 0.7ms. I haven't pinned down where the rest of that second goes, and I would rather say that than guess. What I can say is that a row window is a scrolling optimization. If your complaint is that the table takes a second to appear, this is not the fix for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  A frame number measures your display too
&lt;/h2&gt;

&lt;p&gt;There are two numbers on my own docs that I got wrong, and this is where it showed. I had published 22.5ms against 19.6ms at 1000 rows, and 84.4ms against 18.4ms at 5000. Today the same sweep gives 16.7 against 8.3, and 78.1 against 8.3.&lt;/p&gt;

&lt;p&gt;The 5000 row pair held on the unwindowed side, 84.4ms to 78.1ms, because at 78ms a frame the display is nowhere near the limit. Neither windowed reading held, and the reason is the screen rather than the code. This machine now drives a 120Hz panel, where a frame with nothing to do costs 8.3ms instead of 16.7ms, and both of the old windowed readings were sitting on the 60Hz floor and measuring the display. The 1000 row pair moved on both sides as well, and 16.7ms is exactly two frames on this panel, which means that reading is not far off the floor either.&lt;/p&gt;

&lt;p&gt;A frame interval is only a measurement of your code while your code is the slow part. Publish the refresh rate next to it, or the number is not something anybody else can check.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the window is built
&lt;/h2&gt;

&lt;p&gt;Past 100 rows the body renders what the viewport holds plus six, between two spacer rows that carry the height of everything outside the window. They are spacer rows rather than absolute positioning, because a &lt;code&gt;tr&lt;/code&gt; taken out of flow loses &lt;code&gt;table-fixed&lt;/code&gt;, the sticky head and both pinned columns, and I wanted all three.&lt;/p&gt;

&lt;p&gt;The row height is read off the head row, not a body row. A body row is re-keyed on every scroll, hence a &lt;code&gt;ResizeObserver&lt;/code&gt; on one ends up watching a node that has already been detached, and the scroll height stops following the density: at comfortable it reported 160,272px where it owed 200,040px. The head row carries the same height and keeps its identity for the life of the table.&lt;/p&gt;

&lt;p&gt;The window is arithmetic rather than measurement, and it can be, because every row is exactly the same height. That height is one token on a density scale, where a single attribute on the root element moves row height, control height, padding and label size together, and every component reads it. The window is a side effect of having built that first.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fshmok094gifc38a5b8rb.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fshmok094gifc38a5b8rb.gif" alt="The density knob moving through comfortable, compact and dense" width="600" height="338"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The table belongs to a component registry I've been building for data-dense interfaces. This first appeared at &lt;a href="https://sley-ui.dev/notes/row-window" rel="noopener noreferrer"&gt;sley-ui.dev/notes/row-window&lt;/a&gt;. Happy to answer anything about either.&lt;/p&gt;

</description>
      <category>react</category>
      <category>performance</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
