rage-motion
GitHub SoyRage

416 components · 0 dependencies · MIT

Motion with technique in it

Not a folder of fades. Variable-font axes that bend to the pointer, words that morph letter by letter, carousels built on the browser's own scrolling, menus that actually trap focus.

Browse the catalogue ↓
416
components
0
dependencies
47
modules, all copyable
MIT
take it and ship it

01Everything, listed

Everything, on one screen.

Every component and the attribute that drives it. Most of the kit needs no JavaScript at the call site at all.

Text

  • textRevealdata-rm-text
  • decryptdata-rm-decrypt
  • glitchdata-rm-glitch
  • shinydata-rm-shiny
  • countUpdata-rm-count
  • typewriterdata-rm-type
  • waveTextdata-rm-wave
  • outlinedata-rm-outline
  • rollTextdata-rm-roll
  • countdowndata-rm-countdown
  • split

Typography

  • pressuredata-rm-pressure
  • morphdata-rm-morph
  • curvedata-rm-curve
  • odometerdata-rm-odometer
  • highlightdata-rm-highlight

Buttons

  • filldata-rm-fill
  • shimmerdata-rm-shimmer
  • sparkdata-rm-spark
  • swapdata-rm-swap
  • rippledata-rm-ripple
  • confettijs
  • underlinedata-rm-underline
  • pressdata-rm-press
  • halodata-rm-halo
  • strokeDrawdata-rm-stroke
  • buttonKitdata-rm-btn ×52

Forms

  • floatLabeldata-rm-float
  • autoGrowdata-rm-grow
  • charCountdata-rm-count-chars
  • passwordToggledata-rm-password
  • validatedata-rm-validate
  • rangeFilldata-rm-range
  • fileDropdata-rm-drop
  • stepperdata-rm-steps
  • fieldFocusdata-rm-field-focus
  • submitStatedata-rm-submit
  • mascotdata-rm-mascot
  • successButtondata-rm-success
  • otpdata-rm-otp
  • padlockdata-rm-lock

Phone shapes

  • islanddata-rm-island
  • sheetdata-rm-sheet
  • segmenteddata-rm-segmented
  • frosteddata-rm-frosted
  • springModaldata-rm-modal
  • actionSheetdata-rm-actions
  • toastjs
  • contextMenudata-rm-context

Feedback

  • skeletondata-rm-skeleton
  • spinnerdata-rm-spinner ×6
  • progressBardata-rm-progress-bar
  • dotsLoaderdata-rm-dots-loader
  • pulseDotdata-rm-status
  • badgeCountdata-rm-badge
  • emptyStatedata-rm-empty

Data

  • sparklinedata-rm-sparkline
  • barsdata-rm-bars
  • donutdata-rm-donut
  • gaugedata-rm-gauge
  • statdata-rm-stat
  • starsdata-rm-rating

Text boxes

  • inputKitdata-rm-input
  • searchFielddata-rm-search
  • tagsFielddata-rm-tags
  • selectFielddata-rm-select
  • clearabledata-rm-clearable
  • maskFielddata-rm-mask
  • inlineEditdata-rm-inline-edit

Menu shapes

  • circleNavdata-rm-circle-nav
  • curtainNavdata-rm-curtain-nav
  • hoverSpreaddata-rm-hover-spread
  • breadcrumbsdata-rm-breadcrumbs
  • treeNavdata-rm-tree
  • splitNavdata-rm-split-nav
  • stackNavdata-rm-stack-nav
  • dotNavdata-rm-dot-nav

Texture

  • ringsdata-rm-rings
  • hexGriddata-rm-hex
  • plusGriddata-rm-plus
  • diagonalsdata-rm-diagonals
  • topographydata-rm-topography
  • circuitdata-rm-circuit
  • vignettedata-rm-vignette
  • halftonedata-rm-halftone

Chrome

  • scrollbardata-rm-scrollbar
  • dropdowndata-rm-dropdown
  • tooltipdata-rm-tooltip
  • toggledata-rm-toggle

Surfaces

  • spotlightdata-rm-spotlight
  • tiltdata-rm-tilt
  • borderdata-rm-border
  • cardKitdata-rm-card-kit ×24
  • layersdata-rm-layers
  • edgeLightdata-rm-edge
  • fandata-rm-fan
  • cardParallaxdata-rm-card-parallax
  • glaredata-rm-glare
  • traildata-rm-trail
  • electricdata-rm-electric
  • beamdata-rm-beam
  • blurEdgedata-rm-blur-edge

Fields

  • wavesjs
  • retroGridjs
  • dotGridjs
  • grainjs
  • meteorsdata-rm-meteors
  • sparklesdata-rm-sparkles
  • lampdata-rm-lamp
  • beamsdata-rm-beams
  • meshdata-rm-mesh
  • starfielddata-rm-stars
  • dotsdata-rm-dots
  • stripesdata-rm-stripes
  • scanlinedata-rm-scanline
  • cornersdata-rm-corners

Gallery

  • carouseldata-rm-carousel
  • ringdata-rm-ring
  • deckdata-rm-deck
  • dockdata-rm-dock
  • panelsdata-rm-panels
  • comparedata-rm-compare
  • scratchdata-rm-scratch
  • imageTraildata-rm-image-trail
  • orbitdata-rm-orbit
  • magnetLinesdata-rm-lines
  • coverflowdata-rm-coverflow
  • thumbsdata-rm-thumbs
  • autoplaydata-rm-autoplay
  • wheeldata-rm-wheel
  • peekdata-rm-peek
  • tickerdata-rm-ticker
  • slideshowdata-rm-slideshow
  • dragdata-rm-drag
  • shuffledata-rm-shuffle

Disclosure

  • accordiondata-rm-accordion
  • flipdata-rm-flip
  • expanddata-rm-expand
  • lightboxdata-rm-lightbox

Media

  • imageRevealdata-rm-image-reveal
  • pixelatedata-rm-pixel
  • hoverPreviewdata-rm-preview
  • marqueedata-rm-marquee

Cursors

  • cursorjs
  • targetjs
  • crosshairjs
  • splashjs
  • magneticdata-rm-magnetic

Scroll

  • revealdata-rm-reveal ×50
  • parallaxdata-rm-parallax
  • progressdata-rm-progress
  • horizontaldata-rm-horizontal
  • stackdata-rm-stack
  • scrubdata-rm-scrub
  • skewdata-rm-skew
  • tracingdata-rm-tracing
  • flattendata-rm-flatten
  • stickydata-rm-sticky
  • mosaicdata-rm-mosaic
  • zoomOutdata-rm-zoom-out
  • lineByLinedata-rm-lines-in
  • textMaskdata-rm-text-mask
  • timelinedata-rm-timeline
  • splitScrolldata-rm-split-scroll
  • revealGriddata-rm-reveal-grid
  • pinnedGallerydata-rm-pinned

Navigation

  • pilldata-rm-pill
  • gooeydata-rm-gooey
  • condensedata-rm-condense
  • overlaydata-rm-overlay
  • tabsdata-rm-tabs
  • scrollSpydata-rm-spy
  • progressRingdata-rm-ring-progress
  • commanddata-rm-command
  • sidebardata-rm-sidebar
  • raildata-rm-rail
  • bottomNavdata-rm-bottom
  • megadata-rm-mega
  • pageTransitionjs

03Made of scrolling

Tied to position, never to a timer.

Which means every one of these runs backwards too. An animation that only plays forwards feels broken the instant somebody scrolls the other way — and most of them do.

tracing

A line drawn down the article as you read it: a real path revealed with stroke-dashoffset, which is why it can curve and why the glowing head sits exactly on it.

And here is highlight — the paragraph fills word by word as it crosses the viewport. The unlit state is a muted colour, never an opacity of zero, because prose you cannot read until you have scrolled far enough is a reading tax.

parallax

Back plate Front plate

scrub

--rm-progress runs 0 → 1 as this block crosses the viewport, and the stylesheet decides what that means. Anything expressible in CSS becomes scroll-driven without another component.

flatten

A panel lying flat as it scrolls into view

stack

01

The pin is CSS

Cards pile up and shrink as you pass them. The sticking is position: sticky; only the scale and the brightness are computed.

02

Each one lands lower

Every card sticks a little further down than the last, so the pile stays readable at the top rather than disappearing behind the front one.

03

And it degrades

Under reduced motion the whole effect is dropped and they are simply three cards, in order, at full size.

sticky

Plate for the first panel Plate for the second panel Plate for the third panel

One pinned visual

The pin is CSS. The only JavaScript decides which panel is in the reading position and swaps the image to match.

Text stays text

The panels are ordinary stacked content, so on a narrow screen you drop the sticky rule in a media query and it degrades to a normal article.

No second code path

No JavaScript branch for mobile, no separate template. The same markup, one rule lighter.

horizontal

Vertical scroll distance is mapped to horizontal travel by giving the wrapper height.

It never hijacks the wheel, so the trackpad, the keyboard and the scrollbar all keep working.

Which is exactly what the transform-and-wheel-listener version breaks, every time.

And under reduced motion it becomes a plain horizontally scrollable strip.

04Two states, one handle

Before and after, with the keyboard too.

Almost every before/after slider on the web is pointer-only, which leaves it unusable on a keyboard and invisible to a screen reader. This is a real role="slider": arrows, Home, End and an announced value. Dragging is the enhancement, not the only door.

Without motion With rage-motion

Drag it, or focus the handle and use ← →

05Hand it to your assistant

Teach these components to your AI.

Ask any assistant for a scroll reveal and it writes the same snippet it has written ten thousand times. It is not careless — it has no idea what is already in your project, so it writes the most average thing that could possibly work.

Without MCP

window.addEventListener("scroll", () => {
  document.querySelectorAll(".fade").forEach((el) => {
    if (el.getBoundingClientRect().top < innerHeight)
      el.classList.add("show");
  });
});

A layout read per element per event. And a .fade { opacity: 0 } waiting for the day the script does not load.

With MCP

<div data-rm-reveal="up" data-rm-delay="120">…</div>

It looks before it writes, finds the component you already have, and tells you why it chose it — because the reasoning travels with the component.

Claude Code
Cursor · Claude Desktop

No key, no account, no network. The server only reads files from the installed package.

06Take it with you

Three ways in.

One file

Every component depends only on core/motion.js. Take the two and go — that is how most people will use this, and it is designed for exactly that.

The package

npm i @soyrageagency/rage-motion
import { init } from "@soyrageagency/rage-motion";
import "@soyrageagency/rage-motion/css";
init();

No build

<link rel="stylesheet" href="…/rage-motion.css">
<script type="module">
  import { init } from "…/index.js";
  init();
</script>