Animated Icons Table
Row actions built with Iconimate's motion-driven icons instead of static glyphs. Hover, focus, or tap a row action to preview its animation; click to apply it.
npx shadcn add https://shad-table.dev/r/animated-icons-table.jsonHover a row action to preview its motion; click to apply it.
| Name | Role | Actions | |
|---|---|---|---|
| Amelia Frost | amelia@acme.dev | Engineering | |
| Noah Park | noah@acme.dev | Design | |
| Sofia Reyes | sofia@acme.dev | Product | |
| Liam Chen | liam@acme.dev | Engineering | |
| Maya Okafor | maya@acme.dev | Support |
Installing the icons individually
The icons themselves are published on Iconimate's own registry, not this one — installing animated-icons-table above already pulls all five in, but you can add any of them to another project on their own:
Star
npx shadcn add https://iconimate.app/r/star.jsonBell Ringing
npx shadcn add https://iconimate.app/r/bell-ringing.jsonArchive
npx shadcn add https://iconimate.app/r/archive.jsonTrash
npx shadcn add https://iconimate.app/r/trash.jsonArrows Clockwise
npx shadcn add https://iconimate.app/r/arrows-clockwise.jsonHow it works
1.Every icon exposes an imperative handle, not just `:hover`
Each icon forwards a ref shaped like { startAnimation, stopAnimation }, because `:hover` never fires on touch. The Reset button in the toolbar calls startAnimation() directly on click, so the refresh spin plays even for someone who tapped the button without ever hovering it.
const refreshRef = useRef<IconHandle>(null)function handleReset() {refreshRef.current?.startAnimation()onReset()}<ArrowsClockwiseIcon ref={refreshRef} size={16} />
2.A single useHover() drives motion, focus, and touch together
Every hover-driven icon (star, bell, archive, trash) shares one useHover() hook from #/lib/animated-icon.ts. It wires onMouseEnter/onMouseLeave AND onFocus/onBlur to the same controls, so tabbing to a row action previews its motion exactly like hovering it does — no separate keyboard path to maintain.
return {controls,reduced,ambient,start,stop,bind: { onMouseEnter: start, onMouseLeave: stop, onFocus: start, onBlur: stop },}
3.The animation loops for as long as the pointer stays
start() doesn't just play the "animate" variant once — it replays it end-to-end while the pointer or focus is still on the icon, snapping back to "normal" between cycles (invisible, since keyframes end where they start) so the next replay actually restarts instead of resolving instantly.
void controls.start('animate').then(() => {if (!looping.current) returncontrols.set('normal')if (!ambient) { looping.current = false; return }const elapsed = performance.now() - t0replayTimer.current = window.setTimeout(run, elapsed < 100 ? 300 : elapsed * 0.3)})
4.Row state drives which icon variant renders, not a second icon set
There's no separate "filled star" icon component. The same StarIcon just gets fill: 'currentColor' when member.favorite is true, and its wrapper button picks up an amber text color. Toggling favorite/notify/archived is a plain array map — the same pattern the Editable Table uses for its optimistic updates.
function toggle(id: string, key: 'favorite' | 'notify' | 'archived') {onDataChange((prev) =>prev.map((row) => (row.id === id ? { ...row, [key]: !row[key] } : row)),)}<StarIcon size={18} style={{ fill: member.favorite ? 'currentColor' : 'none' }} />