DEV Community

Cover image for 330,000 free icons for video, and every one of them can draw itself on
Shayan
Shayan

Posted on Originally published at atomcut.net

330,000 free icons for video, and every one of them can draw itself on

AtomCut now ships an Icons panel: Lucide, Tabler, Phosphor, Material Symbols, Simple Icons, the emoji sets, the flag sets, 208 open-source icon sets and 337,000 icons, searchable from inside the editor and dropped onto the canvas as ordinary vector layers. Pick an ink, a size and an entrance in the panel's footer, drag an icon onto the stage or a timeline lane, and it lands drawing itself on: the strokes ink in over 900 ms, one piece after another, in the order the icon was drawn. That is an animated icon for a video, made from a free icon, in one drag. AtomCut is a free motion design and video editor that runs in the browser, no account, no install.

I want to write down the two decisions underneath it, because both look obvious afterwards and neither was the first thing I built.

Where do 337,000 icons come from?

Not from us. Every serious open icon set is published in the Iconify data format: a JSON file per set with the set's name, author, licence, sample icons, and one entry per icon holding its SVG body and viewBox. The same format feeds the Iconify plugin for Figma and the icon pickers in half the component libraries you have used. There is a public API in front of it that lists the sets, lists a set's icons, searches names across all of them, and returns bodies in batches.

My first instinct was a pipeline: pull the JSON, index it, publish it to object storage next to the sound library, ship a search. I got as far as sketching the manifest before noticing I was re-deriving, badly, exactly the three endpoints the API already had. So the editor asks the API. It asks it through a hostname I own, a Cloudflare Worker that caches every answer at the edge and forwards nothing but the four paths the panel uses, with the public API as the fallback if that Worker is ever down. If I later want richer tags than names, that is a different origin behind the same base URL, not a rewrite.

The one thing I did build is a licence gate. The catalogue includes sets under non-commercial and share-alike terms, and a 24px glyph must never put a condition on the video you export. AtomCut offers a set only if its SPDX licence is on a short list that allows commercial use in a rendered artwork, shows the licence on every set's cover, and for CC BY sets names who to credit. That cut 238 sets to 208.

The Icons panel open over the AtomCut studio: a rail of sets on the left, and on the right the families Interface, Material, Emoji and Logos as horizontal rows of set covers, each showing four sample icons, its icon count and its licence
Home. One row per family, the licence on every cover, View all at the end of each row.

Why an icon is not an icon layer

It would have been quick to add an icon clip kind that stores lucide:rocket and renders the body. It would also have been wrong in the way that costs the most later: a new kind of layer is a new row in every table in the app. Selection, keyframes, effects, the SVG exporter, the Lottie translator, morphing, the pen tool, the Layers panel: each would need to learn what an icon is, and until every one had, an icon would be the layer that does less.

So an icon goes in through the SVG importer, the same door a dropped .svg file uses. The importer already splits every disjoint piece of an SVG into its own path layer and groups them. A Lucide rocket therefore arrives as four path layers in a group called Rocket, each with a stroke list, each editable with the pen, each keyframeable, each able to morph into something else. The panel's only job is to decide what the SVG says before it goes in: the ink (which can be a colour variable, so a brand palette retints every icon at once), the landing size, and the stroke width if you want thinner lines than the set drew.

One real bug fell out of doing it this way, and it was in the importer, not the panel. Lucide, Feather and Tabler put the whole look on the root <svg> element: fill="none" stroke="currentColor" stroke-width="2". The importer walked the root's children starting from an empty style, so every icon came in black and filled. Nobody had noticed because nobody had dropped a bare Lucide file before; the panel dropped a thousand of them in an afternoon. The fix made the root part of the cascade, and every SVG import in the app got a little more correct.

A Lucide rocket on the canvas, selected, with the timeline below showing a Rocket group on Track 1 and its four path layers as rows underneath it
What lands: a group of path layers, not an icon object. Everything a path can do, the icon can do.

Draw On, and why it is not a pose

Layer animations in AtomCut are poses: a preset takes a progress from 0 to 1 and returns an opacity, an offset, a scale, a rotation, a blur. Fades, slides, bounces and spins are all that one function. It is deliberately small, because the same pose is read by the canvas, by text glyphs, by the SVG exporter and by the Lottie translator, and none of them has to know which preset produced it.

A stroke drawing itself on is not a pose. It cannot be expressed as where the layer is; it is how much of a line exists. I did not want to teach a pose about outlines, because then a text glyph and a Lottie transform would be carrying a field that means nothing to them. So Draw On is a second channel of the same preset: same id, duration, easing, in and out edges, and the same timeline handle drags it, but only the stroke pipeline reads it. The compositor sees the identity pose and takes its fast path.

The pipeline was ready for it, which is the part I was most pleased about. Every stroke in AtomCut already runs a trim stage, and trim is cut as exact Béziers along arc length. A draw-on is a trim window per strand that opens over time, applied before the stroke's own trim, so a dash of light can still chase around a half-drawn logo. The knobs are the ones After Effects' Trim Paths and every SVG draw-on library converged on:

Knob What it does
Order Together, One by one (along the total length, the pen never lifts), Staggered (equal shares, each a beat after the last)
Stagger For staggered: 0% is together, 100% is back to back, more leaves a pause between pieces
Pieces Bottom piece first, the way the icon was drawn, or top first
Start at Where along each strand the ink begins, 0 to 100%
Direction Forward, backward, or both ways out of the start point
Fill Keep, fade along, or fade late (the fill lands as the last line does)
Pen The width borrowed when a filled icon has no stroke of its own

That last row is what makes it work on Material and Font Awesome, which are silhouettes with no line to draw. Draw On inks the silhouette's edge in the fill colour and then brings the fill in behind it.

A group read-through does the rest. Put Draw On on the Rocket group and its four paths draw as one drawing, bottom piece first, because that is document order in the SVG and the order a person drew it in. A child with its own Draw On keeps it; clear the group's and every child is untouched. It is the same rule a group's fill already followed, and it is the whole deep-customisation story: one setting for the icon, and any piece that deserves its own timing gets its own.

The rocket half drawn on the canvas at frame 12, the Animate tab open on the right showing Draw On at 900 ms with Order set to One by one, From set to Start and Fill set to Fade late, and its ease-in-out curve
Frame 12 of a 900 ms entrance. The Animate tab is the same one every layer has; Draw On is one more tile in it.

Animated icons for video, without the marketplace

The reason this matters more than a picker: an animated icon pack is a product category. Sites sell them by the thousand, as Lottie files or MP4 loops, one motion baked per icon. Here the motion is a setting and the icon is a layer, so the pairing is yours. Any of 337,000 icons, any entrance in the gallery, any easing, any duration, in the brand colour from your variables, exported as MP4, WebM, GIF, PNG sequence, SVG or Lottie from the same render. Change your mind about the timing after the fact and it is a slider, not a re-download.

What it does not do yet

Search matches names. Lucide's own metadata knows that house is also home; that synonym list is not in the data the panel reads, so "home" finds the sets that call it home and misses the ones that call it house. It is on the list, and it is a different origin behind the same base URL when it comes.

Lottie was the export I expected to lose, and did not. A Lottie trim is one window per shape group, keyed over time; a draw-on is exactly that, one per strand, so the exporter bakes the windows it would have drawn, one key per frame, and a filled icon gets the same borrowed outline the canvas gives it. The file is bigger than a hand-authored one would be. It plays the same.

The panel is in the Plugins rail of the editor now. The documentation, with every knob and every family, is at /docs/icons.

Top comments (0)