Interaction logic is behaviour, and behaviour that has already been measured and tested belongs in a package, not in a snippet an agent rewrites on every screen. Two packages hold it:
@enigmax/primitives- interaction behaviour: timing, pointer handling, accessibility. No visual styles.@enigmax/utils- what renders nothing: a short-TTL read cache, and a breached-password check.
Both are plain npm packages. Nothing about them requires enigma - enigma add is a convenience that also lets an agent discover what exists.
Every preview on these pages is a real instance of the published package. Grab this one and throw it.
Installation
Adds the package and prints the import to use. Append --copy to write the source into your project instead.
enigma add --allInstalls with your project's own package manager - npm, pnpm, yarn or bun - read from its packageManager field or its lockfile.
1 Install the dependencies
2 Use it
Nothing to scaffold: the primitives bring no CSS variables and no cn helper, so there is
no init step. Import what you need and style it through its data-* hooks.
The enigma tab adds the package and prints the import to use. The copy tab writes the source into your project instead, so it is yours to edit - the same choice shadcn made, applied to behaviour rather than markup.
--dest <dir> chooses where copies land (default src/lib/enigma, or lib/enigma when the project has no src). --target overrides the detected framework. --dry-run reports without writing.
Copy mode rewrites the internal import specifiers to relative ones, so the copied files stand alone. It refuses to run when the package source is not on disk rather than inventing it.
Which framework
The core of every primitive is framework-agnostic TypeScript with no dependencies, so vanilla JS and Astro work today through the main entry - this page is Astro, and the demo above is the vanilla entry. React has a thin adapter. Vue, Svelte and TanStack adapters are not written yet; they will reuse the same core rather than reimplement it.
| Target | Entry | Marquee export |
|---|---|---|
| Vanilla, Astro | @enigmax/primitives |
createMarquee |
| React | @enigmax/primitives/react |
useMarquee |
Styling: Tailwind by default, CSS on request
The primitives themselves ship no styles and never will. Utility classes inside the engine would break every project without Tailwind and tie a behaviour package to a CSS framework’s major version; being styleless is what makes them work in any design.
The styling lives one layer up, in recipes - complete styled components, not
fragments - that reach your project through enigma add --copy:
enigma add marquee --copy --style tailwind
# lib/enigma/marquee/marquee.ts the headless engine
# lib/enigma/marquee/use-marquee.ts the React hook
# lib/enigma/marquee/Marquee.tsx a styled component, utility classes, yours to editenigma add marquee --copy --style css
# lib/enigma/marquee/marquee.ts the headless engine
# lib/enigma/marquee/use-marquee.ts the React hook
# lib/enigma/marquee/Marquee.tsx a styled component, class names, yours to edit
# lib/enigma/marquee/styles.css the stylesheet it importsYou do not have to pass --style. The default is read from your project: tailwindcss,
@tailwindcss/vite or @tailwindcss/postcss in the manifest means Tailwind, anything else
means plain CSS. Writing utility classes into a project without Tailwind would produce a
component styled by nothing, which is worse than the CSS variant - so “Tailwind by default”
means “where Tailwind is”.
--style none copies the headless files only. And cache has no recipe on purpose: it
renders nothing, so there is nothing to style.
A Tailwind v4 trap worth knowing
Tailwind v4 writes translate-* to the CSS translate property, which composes with
transform rather than replacing it. The marquee drives the track’s transform every
frame, so a translate-x-* utility on that element does not override it - the two add up
and the row drifts. Offset the lane or an inner element instead, never the moved one. Both
marquee recipes carry that warning at the top of the file.
They ship no styles
A primitive applies only what its behaviour requires - overflow, touch-action, user-select, will-change, transform - and never anything that is a look. State is published as data attributes, so the styling stays entirely yours:
[data-enigma-marquee] { cursor: grab; }
[data-enigma-marquee][data-dragging] { cursor: grabbing; }
[data-enigma-marquee][data-hovering] { --logo-opacity: 1; }
[data-enigma-marquee-track] { display: flex; }
Pass manageStyles: false to take even the functional styles back.
A themes package that styles these hooks for you is planned; the attributes are already stable, so anything you write against them now keeps working.
The catalogue an agent reads is the one you installed
enigma add looks for the registry inside the package installed in your project first, and only falls back to the copy bundled with the CLI. So an agent is shown the API your project actually compiles against, instead of a catalogue frozen into a skill that drifts the first time you upgrade.
In this section
- Marquee - draggable infinite row: logo walls, testimonial strips, pill rails.
- Cache - short-TTL read cache with in-flight deduplication.
- Notifications - queue, dedupe and dismiss timers that hold when the tab is hidden.