<?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: Ethan Moore</title>
    <description>The latest articles on DEV Community by Ethan Moore (@ethanmoorenotes).</description>
    <link>https://dev.to/ethanmoorenotes</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%2F4208923%2F26c7b2ad-e5e0-4dcf-a193-de60c6c6bc02.png</url>
      <title>DEV Community: Ethan Moore</title>
      <link>https://dev.to/ethanmoorenotes</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ethanmoorenotes"/>
    <language>en</language>
    <item>
      <title>Writing alt text for animated images in docs (a short checklist)</title>
      <dc:creator>Ethan Moore</dc:creator>
      <pubDate>Thu, 29 Oct 2026 01:00:33 +0000</pubDate>
      <link>https://dev.to/ethanmoorenotes/writing-alt-text-for-animated-images-in-docs-a-short-checklist-1inm</link>
      <guid>https://dev.to/ethanmoorenotes/writing-alt-text-for-animated-images-in-docs-a-short-checklist-1inm</guid>
      <description>&lt;p&gt;Animated GIFs and short looping videos are everywhere in READMEs and docs now: a terminal demo, a UI flow, a before/after. Most of them ship with alt text like &lt;code&gt;demo.gif&lt;/code&gt; or &lt;code&gt;screenshot&lt;/code&gt;, which tells a screen reader user nothing.&lt;/p&gt;

&lt;p&gt;Here's the checklist I've settled on after rewriting the alt text in a few of my own projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Describe the outcome, not the frames
&lt;/h2&gt;

&lt;p&gt;A screen reader user doesn't need "cursor moves to the button, button turns blue, modal opens". They need what the animation proves.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Weak: &lt;code&gt;Animation of the app&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Better: &lt;code&gt;Running the init command creates a config file and prints three setup steps&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the GIF exists to show "it works in one command", say that.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Keep it short, move detail into the text
&lt;/h2&gt;

&lt;p&gt;Alt text is read in one go and can't be skimmed. I aim for one sentence. If the animation shows a multi-step process, the steps belong in the surrounding prose or a numbered list, where everyone benefits from them, and the alt text can just summarise.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Mention text that appears on screen
&lt;/h2&gt;

&lt;p&gt;If the animation types out a message, shows an error, or displays a result value, include that text. It is often the most important part and it is invisible to assistive tech otherwise.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Don't start with "GIF of" or "Image of"
&lt;/h2&gt;

&lt;p&gt;Screen readers already announce that it's an image. Start with the content.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Decorative loops get empty alt
&lt;/h2&gt;

&lt;p&gt;A purely decorative animated banner should have &lt;code&gt;alt=""&lt;/code&gt; so it is skipped, rather than a description nobody needs every time they land on the page.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. For video, the same ideas apply in different places
&lt;/h2&gt;

&lt;p&gt;For a &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; you don't have &lt;code&gt;alt&lt;/code&gt;, so:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;give it a short &lt;code&gt;aria-label&lt;/code&gt; or a visible caption that states what it shows,&lt;/li&gt;
&lt;li&gt;write alt text for the poster image if it is meaningful,&lt;/li&gt;
&lt;li&gt;add captions if there is speech, and&lt;/li&gt;
&lt;li&gt;respect &lt;code&gt;prefers-reduced-motion&lt;/code&gt; and avoid autoplaying long loops for users who opted out of motion.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  7. Read it out loud once
&lt;/h2&gt;

&lt;p&gt;The quickest test: read the page out loud and replace each animation with its alt text. If the paragraph still makes sense, you're done.&lt;/p&gt;




&lt;p&gt;None of this takes long, and it's one of the few documentation improvements that's almost free. If you have a rule of thumb I missed, I'd like to hear it in the comments.&lt;/p&gt;

</description>
      <category>a11y</category>
      <category>documentation</category>
      <category>webdev</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Generating Lightweight Terminal GIFs with asciinema, agg, and gifsicle</title>
      <dc:creator>Ethan Moore</dc:creator>
      <pubDate>Mon, 26 Oct 2026 14:17:48 +0000</pubDate>
      <link>https://dev.to/ethanmoorenotes/generating-lightweight-terminal-gifs-with-asciinema-agg-and-gifsicle-1c7l</link>
      <guid>https://dev.to/ethanmoorenotes/generating-lightweight-terminal-gifs-with-asciinema-agg-and-gifsicle-1c7l</guid>
      <description>&lt;p&gt;When building small web tools and command-line utilities, I frequently need to demonstrate workflows directly inside project README files and documentation sites. Recording the screen with a generic GUI capture tool often yields bulky files: a 10-second terminal clip can easily balloon to several megabytes, stutter on mobile connections, and introduce blurry text compression artifacts.&lt;/p&gt;

&lt;p&gt;To keep documentation snappy, I rely on a pipeline of standard open-source utilities: &lt;code&gt;asciinema&lt;/code&gt; to record timing data, &lt;code&gt;agg&lt;/code&gt; (asciinema gif generator) to render crisp frames, &lt;code&gt;ffmpeg&lt;/code&gt; for palette calculations when fine-tuning, and &lt;code&gt;gifsicle&lt;/code&gt; for lossy payload reduction. This workflow keeps terminal recordings sharp and, for short clips, often gets them down to a few hundred kilobytes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Record Text Streams with asciinema
&lt;/h2&gt;

&lt;p&gt;Instead of capturing screen pixels, &lt;code&gt;asciinema&lt;/code&gt; records the raw terminal output along with its timing. A pause costs only a timestamp, not a stream of identical frames.&lt;/p&gt;

&lt;p&gt;Install &lt;code&gt;asciinema&lt;/code&gt; via your package manager and start recording to a local &lt;code&gt;.cast&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;asciinema rec demo.cast
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run your commands naturally. When you finish, press &lt;code&gt;Ctrl+D&lt;/code&gt; or type &lt;code&gt;exit&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Before moving to the next step, you can edit &lt;code&gt;demo.cast&lt;/code&gt; with any text editor. It is newline-delimited JSON: a header line, then one event per line. You can trim stray events by hand, but for long hesitations it is easier to let tools cap them: &lt;code&gt;asciinema rec --idle-time-limit 2&lt;/code&gt; while recording, or agg's &lt;code&gt;--idle-time-limit&lt;/code&gt; when rendering (agg defaults to 5 seconds).&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Render Crisp Terminal Frames with agg
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;agg&lt;/code&gt; utility converts &lt;code&gt;.cast&lt;/code&gt; timing data into an animated GIF. Unlike a screen recorder, it renders the text itself from a font onto a fixed character grid, using a terminal color theme, so the glyphs come out clean.&lt;/p&gt;

&lt;p&gt;Install &lt;code&gt;agg&lt;/code&gt; with Cargo or grab a prebuilt binary from its GitHub releases page, then generate a baseline GIF:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;agg &lt;span class="nt"&gt;--fps-cap&lt;/span&gt; 12 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--font-size&lt;/span&gt; 14 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--theme&lt;/span&gt; monokai &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--speed&lt;/span&gt; 1.2 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--rows&lt;/span&gt; 18 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--cols&lt;/span&gt; 80 &lt;span class="se"&gt;\&lt;/span&gt;
    demo.cast raw.gif
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few flags make an enormous difference here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;--fps-cap 12&lt;/code&gt;: agg's default cap is 30 fps. For a text terminal, around 12 fps still looks smooth and produces fewer frames.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--speed 1.2&lt;/code&gt;: Speeding up execution slightly keeps visitors engaged without making keystrokes unreadable.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--rows&lt;/code&gt; and &lt;code&gt;--cols&lt;/code&gt;: Override the recorded terminal size, which keeps the output dimensions predictable. Record at the same size, or long lines will wrap differently.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 3: Palette Control with ffmpeg
&lt;/h2&gt;

&lt;p&gt;A GIF frame can use at most 256 colors. If your terminal uses smooth syntax highlighting or gradient progress bars, default conversion algorithms will produce noisy color banding. &lt;/p&gt;

&lt;p&gt;When I need tighter control over color mapping than &lt;code&gt;agg&lt;/code&gt; provides out of the box, I render a lossless intermediate file or pass the frames through &lt;code&gt;ffmpeg&lt;/code&gt; using a two-pass palette generator:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; raw.gif &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"fps=12,palettegen=max_colors=64:reserve_transparent=0"&lt;/span&gt; palette.png

ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; raw.gif &lt;span class="nt"&gt;-i&lt;/span&gt; palette.png &lt;span class="nt"&gt;-lavfi&lt;/span&gt; &lt;span class="s2"&gt;"fps=12 [x]; [x][1:v] paletteuse=dither=bayer:bayer_scale=3"&lt;/span&gt; palette_mapped.gif
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By capping &lt;code&gt;max_colors&lt;/code&gt; to 64 (or 128 for complex themes) and applying an ordered Bayer dither, the file usually shrinks. Terminal backgrounds stay flat, which gives the subsequent LZW compression algorithm repetitive pixel runs to compress efficiently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Final Shrinkage with gifsicle
&lt;/h2&gt;

&lt;p&gt;The raw output from rendering engines often retains duplicate pixels and unoptimized frame metadata. &lt;code&gt;gifsicle&lt;/code&gt; is the standard tool for stripping overhead, dropping redundant frame data, and applying lossy quantization.&lt;/p&gt;

&lt;p&gt;Run this compression pass on your rendered GIF:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gifsicle &lt;span class="nt"&gt;-O3&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
         &lt;span class="nt"&gt;--lossy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;80 &lt;span class="se"&gt;\&lt;/span&gt;
         &lt;span class="nt"&gt;--colors&lt;/span&gt; 64 &lt;span class="se"&gt;\&lt;/span&gt;
         &lt;span class="nt"&gt;--no-extensions&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
         raw.gif &lt;span class="nt"&gt;-o&lt;/span&gt; demo_optimized.gif
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is what these arguments do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-O3&lt;/code&gt;: The highest optimization level. Like the lower levels it stores only the changed part of each frame, but it tries several methods and keeps the smallest (slower to run).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--lossy=80&lt;/code&gt;: Allows small visual noise in exchange for massive file savings. gifsicle's docs suggest values around 30 to 200; on dark terminal backgrounds 60 to 80 is usually hard to notice. Compare before and after on your own clip.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--colors 64&lt;/code&gt;: Reduces the palette to 64 colors. A smaller palette means fewer bits per pixel and longer repeated runs, so LZW compresses better.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--no-extensions&lt;/code&gt;: Drops non-essential extension blocks such as comments and app metadata. gifsicle still keeps the loop setting.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Baseline Reference Numbers
&lt;/h2&gt;

&lt;p&gt;Across my open-source projects, I aim for these constraints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Target size: Under 800 KB for a 15-second clip.&lt;/li&gt;
&lt;li&gt;Frame rate: 12 fps (never exceed 15 fps for CLI tooling).&lt;/li&gt;
&lt;li&gt;Color count: 32 to 64 colors for standard mono-like shells; 128 colors maximum for rich terminal UIs.&lt;/li&gt;
&lt;li&gt;Dimensions: 80 columns by 16 to 24 rows, agg font size 14.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Text-based capture plus strict frame optimization gives you crisp documentation demos that load fast.&lt;/p&gt;

</description>
      <category>terminal</category>
      <category>cli</category>
      <category>gif</category>
      <category>documentation</category>
    </item>
  </channel>
</rss>
