@sharedex/ui
v0.10.0
Published
Headless React components: an animated carousel with pure-function layout effects, page sections (Hero, About, Services, Contact) with layout presets and token-driven default looks, and 88 background effects (CSS, SVG, canvas, WebGL, Three.js) on one chea
Readme
@sharedex/ui
A headless, animated carousel for React — where every visual effect is a pure function.
Coverflow, a fanned hand of cards, a spotlight, a deck, a wave — each is a ~10-line function of one number, not a fork of the library. Physics is a config field. The markup is entirely yours.
React 19 · motion · Tailwind v4 preset · ESM · RSC-safe
The idea
Most carousels give you a component and a bag of boolean props. This one gives you a small engine and four clean seams:
Config for numbers. Pure functions for motion. Children for markup. Hooks for anything that writes.
- Numbers — gap, buffer, autoplay delay, axis — live in one five-section
configobject. - Motion — what a card looks like at a given distance from centre — is a
pure
(buffer, slot, frame) => voidfunction. Swap it, compose it, write your own. - Markup — the card itself — is your component. The library never renders a pixel of content.
- Writes — filtering, stepping, jumping — go through hooks and a typed engine handle.
Everything visual is derived from one unbounded float — a card's signed,
fractional distance from the centre. A slot 1.5 positions to the right is at
distance = 1.5; its transform, opacity and z-index all fall out of that.
Effects
| Preset | What it does |
| --- | --- |
| wave() | Cards at their measured pitch; height swells along a curve peaking at centre. The default. |
| scale() | Centre card full size, every other card smaller the further out it sits. |
| coverflow() | Neighbours turn away in 3D and sink back on the z-axis; distant cards converge into a stack behind the centre instead of stringing across the screen. |
| fan() | A hand of cards, each tilted around a pivot below the deck so the row bows into an arc. |
| spotlight() | One card in focus; the rest shrink, dim, and blur out of it. |
| deck() | A near-solid pile — cards behind nudge down and tilt a few degrees. |
| panorama() | A curved strip seen from inside: the outer cards come closer, look bigger and turn in. |
| expo() | Small cards on one line; the active one opens to full size and pushes the others aside. |
| material() | The Material You hero carousel: one large card, clipped (not squashed) strips of its neighbours. |
| superFlow() | A scale row whose cards lean back against the motion while it moves. |
| barrel() | Cards around the outside of a cylinder; with no angle a full circle that turns forever. |
| helix() | A spiral staircase: each card a step further round the axis and a little further along it. |
| ring() | Cards around a flat circle like a clock face, each pointing outward; an open arc with angle. |
| staircase() | A row on a slanted track that also runs into the page: the next cards climb up and back. |
| fallAway() | Leaving cards drop (or rise), turn, shrink, blur and fade with their distance from the focus. |
| sphere() | Cards on a globe that turns so the focused card faces you; drag walks a loop over the whole sphere. |
// A subtle 3D lean, in full — this is a complete, usable effect:
export const tilt = () =>
composeLayout(
"tilt",
track({ overlap: 0.15 }),
curve("rotateY", [-1, 0, 1], [12, 0, -12]),
curve("opacity", [-2, 0, 2], [0.4, 1, 0.4]),
);Physics is just as swappable — momentumSnap (flick, decay, land on a slot),
snapToNearest (spring to the closest), freeScroll (coast to a stop), or
pagedBy(n) (advance in groups). One config field each.
Beyond one row
- Speed looks:
modify(layout, speedWave(), speedTilt())adds a wave or a lean while the cards move, exactly zero at rest. - Scroll-driven:
useScrollLink(engine, { pxPerCard })lets the page scroll turn the carousel. - Morphs: change
config.layoutand every card springs from the old shape into the new one (list → helix → sphere), each a few ms after its neighbour. - Enter and exit:
engine.enter/engine.exittake a preset (presence.fade,contract,expand,scatter,slideAlong) for the first reveal, filter swaps andengine.hide()/show(). - Open views:
engine.open: openView.detail()(a big card over a thumbnail strip),panel()(with<Carousel.Panel>for your text) orinline();engine.open(i)/close(), Esc. - Stacks:
stage(cardStack({ exit: "throw" }))is a deck you swipe cards off.
How it's built
- One source of truth.
positionis an unbounded float in index space, not pixels. There is no scroll container, no accumulated offset, no ring width to measure — a slot's placement is a pure function of its own distance. - One write per slot per frame. A layout resolves into a single serialized
transformstring, which short-circuitsmotion's 18-property serialisation loop. A rich seven-term coverflow costs the same as a two-term wave. - Virtualized. Only
2·buffer + visibleslots mount, however many items exist. Looping is a modulo, not cloned nodes. - Zero-allocation reads. Frame and slot state are read into caller-owned objects reused every frame — no garbage on the hot path.
- Split contexts. A motion slice (stable refs / MotionValues) and a data slice (filter, metrics) so a hover or a resize doesn't re-render every card.
- Pixel-identity gated. The
wavepreset is frozen and tested bit-for-bit againstmotion's own interpolator, so the core animation can never silently drift.
Install
npm install @sharedex/uiPeers: react 19, react-dom 19, motion 12, 13 or 14; three ≥ 0.165 only for
@sharedex/ui/three (optional). ESM only, ships compiled
dist/ with types. SSR / React Server Component safe (a "use client" island,
no next dependency).
Wire up Tailwind (v4)
In your Tailwind entry stylesheet, after @import "tailwindcss";:
@import "tailwindcss";
@import "@sharedex/ui/theme.css";theme.css registers three namespaced color tokens (--color-sx-primary,
--color-sx-secondary, --color-sx-accent) and tries to add this package's
compiled code as a scan source.
If a component renders unstyled, add an explicit source (adjust the ../
depth to your node_modules):
@source "../node_modules/@sharedex/ui/dist/**/*.js";Theming / dark mode
Override the tokens in your own @theme (it runs after the import and wins), or
under a dark selector ([data-theme="dark"], @media (prefers-color-scheme:
dark)). The carousel reads only those three tokens.
[data-theme="dark"] {
--color-sx-primary: #e7f2f1;
--color-sx-secondary: #0b1416;
--color-sx-accent: #6c5ce7;
}The package ships no --breakpoint-* — the sm: / md: / lg: steps in
your slotClassName resolve to your own breakpoint scale.
A taste
import { Carousel, coverflow, snapToNearest } from "@sharedex/ui";
const CONFIG = { layout: coverflow(), physics: { settle: snapToNearest() } };
const SLOT = "w-[240px] sm:w-[280px] h-80"; // width/height here IS the pitch
export function Templates({ items }) {
return (
<div className="mx-auto w-full max-w-3xl">
<Carousel.Root items={items} config={CONFIG} slotClassName={SLOT}>
<Carousel.Viewport>
<Carousel.Track>
<Carousel.Window>
{(slot) => (
<Carousel.Slot key={slot.key} index={slot.index}
ringIndex={slot.ringIndex} itemIndex={slot.itemIndex}>
{slot.item ? <MyCard item={slot.item} /> : <MyPlaceholder />}
</Carousel.Slot>
)}
</Carousel.Window>
</Carousel.Track>
</Carousel.Viewport>
</Carousel.Root>
</div>
);
}Drag and autoplay come for free from Track. Arrows, filter pills and
pagination you build from the engine handle — a render-prop form of Root hands
it to you.
- Memoize
items(and a customfiltersarray, and theconfigobject). A new identity on every parent render rebuilds the slot ring / re-resolves the engine config. - Wrap a horizontal carousel in a parent with a definite width; give a
vertical one (
config={{ engine: { axis: "y" } }}) a sized viewport.
Controls & filters
Built-in Controls / Filters components are on the roadmap. For now use the
render-prop form of Root to reach the engine handle:
<Carousel.Root items={ITEMS} filters={FILTERS} config={CONFIG} slotClassName={SLOT}>
{(engine) => (
<>
{FILTERS.map((f) => (
<button key={f.value} aria-pressed={engine.filter === f.value}
onClick={() => engine.setFilter(f.value)}>
{f.label}
</button>
))}
<Carousel.Viewport>{/* … Window … */}</Carousel.Viewport>
<button onClick={() => engine.stepBy(-1)}>‹</button>
<button onClick={() => engine.stepBy(1)}>›</button>
</>
)}
</Carousel.Root>A filter swap fades the carousel out and back in. With engine: { filterTransition: "view" }
the browser's View Transitions API plays it instead: cards in both lists move to their new
place and the rest fade (a plain fade where the API is missing or motion is reduced).
config is a five-section partial (engine, physics, autoplay, layout,
a11y). Build a project-wide baseline once with defineCarouselDefaults(...)
and pass it as base=.
Anatomy
flowchart TD
Root["Carousel.Root — items, config, filters, the engine"]
Viewport["Carousel.Viewport — measured region, gestures, sizing probe"]
Track["Carousel.Track — drag + autoplay, the reveal / cross-fade"]
Window["Carousel.Window — virtualization, one WindowSlot per mounted position"]
Slot["Carousel.Slot — the resolved transform; your card inside"]
Root --> Viewport --> Track --> Window --> SlotPage sections
Since 0.3.0 the package also ships headless page sections: Hero, About,
Services and Contact. Each gives you layout presets, a typed content
shape and the right semantics; the look is yours.
import { Hero } from "@sharedex/ui";
<Hero.Root
layout="split" // "split" | "centered" | "media-background"
content={{
title: "Fresh bread every morning",
actions: [{ label: "Order for tomorrow", href: "/order" }],
media: { kind: "image", src: "/bread.jpg", alt: "Loaves on a rack" },
}}
/>- Default looks come with
theme.css: plain CSS in Tailwind'scomponentslayer, built only on thesx-*tokens. Any class you pass viaclassNamesits in the laterutilitieslayer, so it always wins. unstyledon a section'sRootdrops the look and keeps the layout.- Layouts use container queries, so a section responds to the width it gets, not the viewport, and mirrors correctly in RTL.
- Guides for Claude Code ship in
guides/(node_modules/@sharedex/ui/guides/hero.md…). backgroundon any section'sRoottakes a decorative layer (below).revealon any section'sRootmakes its main parts appear one after another as it scrolls into view (reveal,reveal={blurFade()},reveal={{ effect, stagger, delay }}; see Transitions below).
Backgrounds
Since 0.4.0: 88 background effects (patterns, gradients, particles, scroll effects, shaders,
fluid, 3D) as pure descriptions, plus image() / video() slots for your own files. One runtime
under <Background.Root> pauses them off screen, honours reduced motion, caps the pixel ratio and
picks a still fallback on devices too weak for an effect.
import { Background, Hero, aurora, filmGrain } from "@sharedex/ui";
<Hero.Root
content={heroContent}
background={
<Background.Root>
<Background.Layer effect={aurora({ speed: 0.6 })} />
<Background.Layer effect={filmGrain({ opacity: 0.08 })} />
</Background.Root>
}
/>- Pay for what you mount. CSS effects inject their stylesheet only when used; WebGL effects run on a ~2 KB runner (no Three.js) and give their context back while off screen.
- Three.js effects (
liquidGlass,chrome,neonTubes…) live in a separate entry,@sharedex/ui/three, withthreeas an optional peer:npm i threeonly if you use them. - Tiers. Every effect is tagged
all,midorhigh(from the owner's Background Effects Atlas); heavier ones render their CSS fallback until the device is known, during SSR, and on weaker devices. - Guide for Claude Code:
node_modules/@sharedex/ui/guides/backgrounds.md.
Transitions
Since 0.5.0: 40 transitions that play at a moment, every one a function of one number p
(0 = hidden, 1 = shown): 8 one-card carousel looks, 18 CSS / SVG masks and reveals, 14 WebGL
shaders on pictures. The same transition plays in a one-card carousel (dragging scrubs it) and as
a section reveal.
import { Carousel, Reveal, Services, stage, cube, fadeUp, wipe, blurFade } from "@sharedex/ui";
<Carousel.Root items={slides} config={stage(cube())}>…</Carousel.Root> // one card at a time
<Reveal effect={wipe({ from: "start" })}><img src="/team.jpg" alt="" /></Reveal>
<Services.Root content={services} reveal={blurFade()} /> // parts one after another- Reveals:
Reveal,Reveal.Group(staggered),trigger="scroll"to follow the scroll, and therevealprop on every section. - Shaders (
noiseBurn,ripple,displacement,pageCurl…) blend pictures on one canvas (Carousel.Shader,<Reveal image>); weaker devices play a CSS fallback. - Scroll compositions:
Curtain,StackCards,PinnedExpand,TextMaskZoom,SplitText, on native scroll timelines with a shared fallback. - Multi-card looks
panorama,expo,material,superFlow, andengine.filterTransition: "view"for filter swaps with the View Transitions API. - Reduced motion and RTL are handled; no animation library beyond
motion. - Guide for Claude Code:
node_modules/@sharedex/ui/guides/transitions.md.
Ticker
A continuous marquee for items of any size, its own component (not a carousel option).
import { Ticker } from "@sharedex/ui";
<Ticker items={tools} velocity={60} gap={12} fade={64} hoverSpeed={0.2} drag aria-label="Tools we use">
{(tool) => <span className="rounded-full border px-4 py-2">{tool}</span>}
</Ticker>- Drivers: time (
velocity, px/s, the sign is the direction), anyMotionValueasoffset(page scroll for bands that move with the page, one each way), anddragwith a glide on release. They add up. - Loops without a jump: the set is repeated just enough to cover the box plus one set, and one
transform moves the row by the offset modulo the set's length. One
ResizeObservermeasures. axis="y"for columns,fadefor a CSS mask on both edges,hoverSpeedto slow or pause it under the pointer or keyboard focus. RTL runs towards the reading start; reduced motion keeps it still; copies arearia-hiddenandinert; the time loop stops off screen.
Grid
An endless plane of cards you pan in any direction, flat or leaning back in 3D. Its own component.
import { Grid, gridOpen, type GridCell } from "@sharedex/ui";
<Grid.Root items={pieces} columns={6} gap={28} stagger={0.5} tilt={{ x: 24, z: -8 }} bulge={140} fog snap
open={gridOpen.panel()} cellClassName="w-[200px] h-[250px]">
<Grid.Viewport className="h-[80vh]" aria-label="Collection">
{(cell: GridCell<Piece>) => <Card item={cell.item} />}
</Grid.Viewport>
<Grid.Panel>{(piece: Piece) => <Notes piece={piece} />}</Grid.Panel>
</Grid.Root>- Endless, virtualized: items are laid out in rows of
columnsand repeat by position (no copied data); only the cells on screen are mounted.staggerbricks the rows;repeat: "blank"leaves a short last row empty. - Drag in any direction with a glide;
snaplands on a card. On a tilted plane the card under the finger stays under it. Opt-inwheelpanning and an idledrift. - Looks:
tilt(one 3D stage),bulge(a dome towards you at the centre),fog(fade with distance), or your own per-cellmodifiers. - Open views:
gridOpen.zoom(),strip()(a big card over a thumbnail strip) andpanel()(room forGrid.Panel); the open card stands upright even on a tilted plane. - One tab stop with arrow keys, Enter to open, a live region naming the centre card; RTL; reduced motion.
Buttons
Button wears any number of effects at once: 73 effects from the owner's Button Effects Atlas (fills,
borders, glow, depth, cursor, label and icon, press, state, idle, glass), each a plain descriptor.
import { Button, Hero, useButtonAction, glowShine, cursorMagnet, stateSwap } from "@sharedex/ui";
const primary = [glowShine(), cursorMagnet()]; // module level, like every effect
<Button effect={primary}>Book now</Button>
<Button as={Link} href="/shop" variant="outline">Shop</Button>
const save = useButtonAction(async () => { await post(); }, { resetAfter: 2000 });
<Button {...save} effect={stateSwap()} labels={{ loading: "Saving", success: "Saved" }}>Save</Button>
<Hero.Root content={content} actionEffect={primary} /> // the Hero's first action wears them- The element you click never moves. Hover and press come from a still outer element; what you see moves
inside it, so a magnet or a lift never flickers at the edge.
classNamestyles what you see,hitClassNamethe outer element. - Effects stack (each paints its own layer, and two that would fight over one motion are reported in dev),
ship their stylesheet only when mounted, and turn off under reduced motion. Heavy ones wear a
-liteversion on weaker devices. - Server Components: almost every effect is plain data. Six carry code (
pressSpark,pressBurst,pressConfetti,pressSpray,labelScramble,glassRefract) and need a client page. - Busy is not disabled:
state="loading"keeps focus, blocks a second click and announces the words. - Sections:
actionEffectonHero.Rootrenders its actions asButtons. Left out, they stay plain links. Buttons follow the new--radius-sx-buttontoken (the control radius unless a design sets its own). - Guide for Claude Code:
node_modules/@sharedex/ui/guides/buttons.md.
Text
Text wears any number of text effects: 69 effects from the owner's Text Effects Atlas plus rows found online
(entrances, fills, outlines and drawings, scramble, scroll, hover, glitch and warp, depth), each a plain descriptor.
Typewriter, WordCycle, NumberRoll and TextPath cover text that changes or follows a shape; Text3D is in
@sharedex/ui/three.
import { Text, Hero, About, WordCycle, NumberRoll, enterRise, inkGradient } from "@sharedex/ui";
const title = [enterRise({ by: "chars" }), inkGradient()]; // module level, like every effect
<Text as="h2" effect={title}>Fresh bread, every morning</Text>
<h1>We bake <WordCycle words={["bread", "cakes", "pies"]} look="flip" /></h1>
<NumberRoll value={1250} format={{ style: "currency", currency: "EUR" }} />
<Hero.Root content={{ title: "We build", titleWords: ["sites", "apps"] }} titleEffect={title} titleCycle={{ look: "flip" }} />
<About.Root content={content} statRoll={{ look: "spin" }} /> // "40+" rolls to 40, keeps its +- The words a reader gets and the pieces that move are two different things. One readable copy for screen readers and search engines; the moving letters, words, lines, copies and canvases are hidden from them. Words and letters are cut on the server, so the HTML holds the real text; nothing is hidden without a way back.
- Effects stack (an entrance with a fill or a hover; two on one channel are reported in dev), letters are cut only where they stand alone (Arabic moves by the word), heavy ones wear a lighter version on weaker devices, and reduced motion shows the text still, in its look.
- Sections: every
RoottakestitleEffectandeyebrowEffect; the Hero'scontent.titleWords+titleCycleadd a cycling word; About'sstatRollrolls the figures. Left out, the markup is as before. - Server Components: most effects are plain data; 17 carry code or pick by device and need a client page (the guide lists them).
- Guide for Claude Code:
node_modules/@sharedex/ui/guides/text.md.
Forms
Form controls whose real native element does all the work (the label, the keyboard, autofill, password managers,
screen readers, a plain form submit), wearing any number of the 91 form effects from the owner's Form Effects
Atlas plus rows found online: Field (with built-in tools: clear, show password, Caps Lock, counter, strength, an async
check, a hint, auto-grow, inline), Checkbox, CheckGroup, RadioGroup, Switch, Select, our own Combobox,
Range, NumberField, OtpField, TagsField, ChipGroup, FileDrop, FormSteps, FormReveal, FormSwap,
InlineEdit, ErrorSummary, SaveStatus, PasswordRules and FormBuddy, a sign-in companion of our own.
import { Controller } from "react-hook-form";
import { Field, Checkbox, Combobox, inputFloat, validColor, checkDraw, pickerOpen } from "@sharedex/ui";
const fieldEffects = [inputFloat(), validColor()]; // module level, like every effect
<Field label="Email" type="email" effect={fieldEffects} error={errors.email?.message} {...register("email")} />
<Checkbox label="I agree" effect={checkDraw()} {...register("terms")} />
<Controller name="topics" control={control} render={({ field }) => (
<Combobox label="Topics" multiple options={topics} effect={pickerOpen()} {...field} />
)} />- Every prop and the
refgo on the native control, so React Hook Form'sregister()spreads straight onto it; a list or files go through aController. The package imports no form library: React Hook Form + Zod is a documented recipe. Errors need words:errorsetsaria-invalid, pointsaria-describedbyat the message and announces it. - Effects read the control's state through CSS (
:focus-visible,:user-invalid,:checked,:autofill), so nearly all are plain data a Server Component can pass; three carry code (checkConfetti,rangeElastic,inputCycle). They never scold while you type, keep their state under reduced motion and drop the movement. - Guide for Claude Code:
node_modules/@sharedex/ui/guides/forms.md.
Option schemas (@sharedex/ui/schema)
Every configurable piece describes its options as data, for tools such as the sharedex Design System Builder: one form can edit any option, a config stores only what differs from the defaults, and a generator writes the same values back as code. The entry is data only (no React, no DOM, no effect code).
import { schemaOf, defaultsOf, sparseOptions, validateOptions, factoryPath, factoryArgs } from "@sharedex/ui/schema";
const schema = schemaOf("background", "water-ripple")!; // fields: kind, label, range, default, help…
defaultsOf(schema); // { strength: 0.8, … } (auto options left out)
sparseOptions(schema, { strength: 0.8, rain: false }); // { rain: false }: the default strength is dropped
validateOptions(schema, { strength: 3 }, schemaOf); // ["strength: must be between 0 and 1"]- Families:
background(88 effects + theimage/videoslots),transition(40),reveal,carousel-layout(16 presets),carousel-modifier,carousel-presence,carousel-open,carousel-physics,carousel(the whole config as one form),ticker,grid,grid-open,scroll(the five compositions),button(73 button effects),text(69 text effects),word-cycle,number-roll.SCHEMAS[family]lists them,schemaOf(family, id)finds one. - Every option is covered.
defineSchema<Options>()requires a field (or acodeentry, for functions, MotionValues and React nodes) for every key of the options type, so a piece that gains an option without a field fails to compile. Tests check that each default is what the piece really does with the option left out. - Forms for components:
CAROUSEL_SETTINGS,TICKER_SETTINGS,GRID_SETTINGS,WORD_CYCLE_SETTINGSandNUMBER_ROLL_SETTINGSare their props as one flat form (CarouselSettingsputs each key back in its section withCAROUSEL_SECTIONS; a Ticker'sdrivestands for itsoffset; a Grid's unsetcolumnsis"auto"). - Calling a factory:
factoryPath(schema)(["presence", "contract"]) andfactoryArgs(schema, options)(positional forpagedBy(n, maxPages)), so a form, a generator and tests call it the same way.
Status
0.10.0 adds the form controls and 91 form effects (every row of the Form Effects Atlas, plus 62 found online):
Field and its tools, Checkbox, CheckGroup, RadioGroup, Switch, Select, Combobox, Range, NumberField,
OtpField, TagsField, ChipGroup, FileDrop, FormSteps, FormReveal, FormSwap, InlineEdit, ErrorSummary,
SaveStatus, PasswordRules, FormBuddy, focusFirstError; the danger and success tokens; the form schema
family. 0.9.0 added Text and 69 text effects (every row of the Text Effects Atlas, plus 26 found online), Typewriter,
WordCycle, NumberRoll, TextPath, Text3D (in @sharedex/ui/three), the Ticker's scrollBoost, and on the
sections titleEffect / eyebrowEffect (every Root), a cycling word in the Hero's title (titleWords,
titleCycle) and rolling figures in About (statRoll); schema families text, word-cycle, number-roll.
0.8.0 added Button and 73 button effects (every row of the Button Effects Atlas but the Lottie / Rive
recipe, which is in the docs), useButtonAction, an actionEffect prop on Hero.Root, the --radius-sx-button
token and the button schema family. 0.7.0 added the option schemas (@sharedex/ui/schema): every background, transition, carousel piece and
setting, the Ticker, the Grid and the scroll compositions described as data, the source of the Design System
Builder's forms and export. Sections (<Hero.Root> …), Reveal.Group and the layout ids now work in React
Server Components (Next.js App Router): their compound objects were built inside client modules, where the
server sees only an opaque reference. The Grid now measures in CSS px under a scaled ancestor (a zoomed preview,
a modal scaling in), as the carousel already did. 0.6.0 added the gallery effects: six 3D shapes (barrel, helix, ring, staircase, fallAway,
sphere) on closed rings and place3d(), speed looks (speedWave, speedTilt), useScrollLink,
layout morphs, enter / exit presets, open views with Carousel.Panel, the cardStack look, and two
new components: Ticker (a marquee driven by time, scroll or drag) and Grid (an endless plane you
pan in any direction, with tilt, bulge, fog and open views). 0.5.0 added transitions (40 effects: one-card carousel looks, CSS masks and reveals, WebGL
shaders), Reveal, five scroll compositions, four multi-card presets, View Transitions filter
swaps and a reveal prop on every section. 0.4.0 added Background (88 effects in CSS, SVG,
canvas, WebGL and Three.js, the @sharedex/ui/three entry) and a background prop on every
section. 0.3.0 added
the page sections and an expanded sx-* token contract to the
0.2.0 headless carousel (engine, six layout presets, four physics policies,
per-slot choreography hooks). A dark-themed documentation site has live demos
of both. It is not published to npm yet.
Shipped: the compound carousel API, effects, physics, card-design hooks,
category filtering, drag, autoplay; Hero, About, Services and Contact sections;
backgrounds (all 94 rows of the Background Effects Atlas: 88 effects, 3 rows covered by the
image() / video() slots, 3 docs recipes); transitions (all 51 rows of the Transition Effects
Atlas); the gallery effects, Ticker and Grid; Button (75 of the 76 rows of the Button Effects Atlas, the
last a docs recipe); Text (all 94 rows of the Text Effects Atlas: 69 effects and the text components); the form
controls (all 134 rows of the Form Effects Atlas: 91 effects and the controls).
Next: built-in Controls / Filters / Pagination components, full a11y wiring for the carousel
(region label, live region, roving tabindex), RTL for the carousel.
Local development
This package lives in the sharedex monorepo
(packages/ui). From the repo root:
npm install # installs every workspace and builds dist/ (the root postinstall)
npm test # node:test — kernel + preset placement (pixel-identity gate)
npm run dev:docs # the dark documentation site, with every effect liveLicense
MIT © Mohamad Faseh
