npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

easeful

v0.1.3

Published

Enter and exit animations for components that already have open and closed state. One attribute, and no JavaScript in the browser.

Downloads

573

Readme

easeful

Enter and exit animations for components that already have open and closed state. One attribute, and no JavaScript in the browser.

<Dialog.Content data-motion="scale-fade" />

That is the whole API. The library matches on the state attributes the host component already writes, so there is nothing to wire up and no wrapper to add. It works with Radix UI, Base UI, the native <dialog> and [popover] elements, and any element you toggle hidden on.

Install

npm i easeful
npx easeful init

init does the two setup steps below plus the lint rule, printing the diff and asking before it writes. npx easeful doctor checks them and exits non-zero, so it can run in CI. Both are idempotent, and both decline rather than guess when a project is shaped in a way they cannot read.

Any React app: Next, Vite, React Router, Astro with React islands. The stylesheet is plain CSS and works anywhere. The types are React only.

Import the layer once, globally. If Tailwind v4 is present, the @layer line has to come before any @import, because a layer's position is fixed where it is first named. Get that wrong and the library fails silently: the attribute is there, the CSS is loaded, and nothing animates.

@layer easeful, theme, base, components, utilities;
@import "easeful";
@import "tailwindcss";

Then activate the types, for autocomplete on the attribute and its values. They do not enforce anything: TypeScript leaves JSX attributes with hyphens in their names unchecked, so data-motion="nonsense" compiles. Annotate a value to get a real error. React only, and the stylesheet works with or without this step.

// easeful-env.d.ts, or any declaration file your tsconfig includes
/// <reference types="easeful/types" />

The types array in tsconfig.json works too. It also switches off automatic inclusion of every other @types package, so node and anything else ambient has to be listed beside it, which is why the reference file is the instruction here.

For enforcement rather than autocomplete, the package also ships an ESLint rule. It reads the same motion.json the CSS is generated from and fails on an unknown preset, a repeat, more than two, a pair that does not compose, or a bad data-motion-state. ESLint 9 or newer, flat config, two lines:

// eslint.config.mjs
import easeful from "easeful/eslint"

export default [...easeful.configs.recommended]

It reads literal values and conditional branches. data-motion={value} is source a linter cannot resolve, so it is left alone.

Coding with an agent

AGENTS.md ships in this package and is generated from the same manifest as the CSS, so the vocabulary an agent reads is the vocabulary the stylesheet matches. For Claude Code, the same body is a skill:

npx easeful skill            # .claude/skills/easeful/SKILL.md, commit it
npx easeful skill --global   # or once, for every project on the machine

It is also at easeful.sanyam.sh/skill.md.

Use

Add the attribute to anything that already signals open and closed state. The second half of the match is written for you, by Radix, by Base UI, or by the browser.

<Dialog.Overlay data-motion="fade" />
<Dialog.Content data-motion="scale-fade" />

Four presets: fade, scale-fade, slide-up, collapse. Compose two with a space: data-motion="fade slide-up". One escape hatch, data-motion-state, for a host that publishes no open and closed pair of its own.

AGENTS.md in this package is the full surface, generated from the same manifest the CSS, the types and the lint rule are, so an attribute the types offer is an attribute a rule matches. Since the compiler will not police the value, that file and the linter are the authority. It is written for a coding agent and it is the shortest complete reference for a person too.

Two rules worth knowing

A collapse panel takes a single element child, and that child is what gets clipped, so padding belongs on it rather than on the panel. The preset animates a grid row rather than a height, because height: auto is not interpolatable and the property that makes it so is Chromium-only.

Do not put Framer Motion on the same element. Both want the transform. easeful writes the translate and scale longhands specifically so your own transform survives, but two libraries animating one element take turns overwriting each other.

Overriding it

Everything ships inside @layer easeful at deliberately low priority, so your own unlayered CSS wins with no !important. Retiming every preset at once is one declaration:

:root {
  --motion-duration: 240ms;
  --motion-ease: cubic-bezier(0.2, 0.9, 0.1, 1);
}

Docs

Every component, shown with the attribute and without it, at easeful.sanyam.sh. Every page is also served as markdown at its own path plus .md, indexed at /llms.txt.

MIT licensed.