Skip to content

tabs

aa-tabs is one feature with two visual patterns. Pair toggles with aa-tabs-content for an expanding accordion. Pair them with aa-tabs-visual for a tab interface. Mix both, or stack autoplay and scroll modes on top — the same primitives drive everything. Modes are space-separated tokens inside the attribute value.

TokenEffect
(empty)Default. One tab open at a time. Clicking the active toggle closes it.
singleOne open at a time, but the active toggle can’t be closed by clicking it. First toggle (or aa-tabs-initial) opens at init.
multiMultiple tabs open simultaneously. Each toggle is independent.
scrollPinned section; scrolling scrubs through tabs sequentially. Touch devices fall back to single. Optional click-to-jump on toggles.
noneDon’t init at this breakpoint. Combine with the | shorthand or -sm / -md / -lg / -xl suffix attributes for responsive on/off.

Autoplay is a separate, standalone attribute — aa-autoplay — shared with aa-slider so the configuration shape is uniform across components. Adding aa-autoplay to a tabset activates autoplay mode and overrides the aa-tabs mode token; aa-tabs="scroll" + aa-autoplay is a conflict and autoplay wins (a dev-mode warning is logged).

aa-autoplay valueEffect
(attribute absent)No autoplay; mode comes from aa-tabs.
(empty: aa-autoplay)Autoplay enabled at the init default interval (autoplay.interval, default 4s). No hover pause.
<number> (e.g. "4")Autoplay with the given dwell in seconds.
hover-pauseAdd hover-pause: mouseenter pauses, mouseleave resumes (non-touch only).
<number> hover-pauseBoth — e.g. aa-autoplay="4 hover-pause".
noneOpt out (e.g. aa-autoplay="4|none" — autoplay on desktop, off on mobile).

Per-toggle override: place aa-autoplay="6" on a single aa-tabs-toggle to vary that tab’s dwell while keeping the rest at the root (or init) value.

<!-- Accordion (FAQ): default mode -->
<div aa-tabs></div>
<!-- Tab interface: visual-only children, single mode -->
<div aa-tabs="single"></div>
<!-- Autoplay carousel-style with hover pause -->
<div aa-tabs aa-autoplay="4 hover-pause"></div>
<!-- Scroll-driven on desktop, plain accordion below md -->
<div aa-tabs="scroll|"></div>
AttributeWhat it marks
aa-tabsTabs root.
aa-tabs-toggleClickable header / tab. ID is auto-assigned if you omit a value. Gets tabindex="0" and ARIA roles automatically.
aa-tabs-contentInline expanding panel — height tweens between 0 and natural on open / close. Pair with a toggle by matching the toggle’s ID.
aa-tabs-visualCross-fade panel — autoAlpha tweens between 0 and 1. Stacked layout (e.g. position: absolute; inset: 0) is the author’s job.
aa-tabs-wrapperOptional wrapper around toggle + content. When present, receives aa-tabs-status="active" for shared CSS targeting.
aa-tabs-initialMark a toggle (or wrapper) to open at init. For single / autoplay / scroll modes, the first toggle is used if you omit it.
aa-tabs-progressInside an aa-tabs-toggle. Value is width (default), height, or circle. Used in autoplay and scroll modes.
aa-tabs-statusSet by the lib on the wrapper (or toggle, when no wrapper). Values: active / inactive. Style off it for hover and active states.
AttributeDefaultNotes
aa-duration(init)Seconds for the height + visual cross-fade. Override per toggle / per content / per visual.
aa-autoplay(off; init interval when present)See the autoplay table above. Per-toggle aa-autoplay="6" overrides that tab’s dwell.
aa-ease(init)Any GSAP ease — height tween, visual fade, and progress bar.
aa-distance30(scroll) vh per tab. Total pin range = N × distance.
aa-scroll-starttop 20%(scroll) ScrollTrigger start position for the pin.
aa-scrubtrue(scroll) ScrollTrigger scrub. Pass a number for scrub smoothing (in seconds).

gsap always; ScrollTrigger for autoplay (viewport gate) and scroll (pin) modes. The scroll-mode click-to-jump animates the page itself with a short gsap tween — no ScrollToPlugin required. If Lenis is active (smoothScroll: true and the script is loaded) the click hands off to lenis.scrollTo so it blends with Lenis momentum.

The lib applies the right roles based on which children each toggle has:

  • Toggle + contentrole="button" + aria-expanded on the toggle, role="region" + aria-labelledby on the content. (Visual, if also present, is treated as decorative — aria-hidden flips with active state.)
  • Toggle + visual onlyrole="tab" + aria-selected on the toggle, role="tabpanel" + aria-labelledby on the visual. The toggles’ parent gets role="tablist" when every toggle in the set is visual-only.

Toggles get tabindex="0", IDs, and aria-controls linkage automatically.

aa-animate elements inside an [aa-tabs-content] or [aa-tabs-visual] are automatically driven by tab activation: they play when the tab becomes active and reverse when it becomes inactive. No aa-trigger attribute needed — the lib emits tab-active / tab-inactive events on the toggle, content, and visual.

<div aa-tabs>
<div aa-tabs-toggle="1">Tab one</div>
<div aa-tabs-content="1">
<p aa-animate="fade-up">Body copy fades up on open and reverses on close.</p>
</div>
</div>

Set aa-trigger="scroll" to opt out of inference for a specific element.

Toggles get tabindex="0" so they’re reachable via Tab. With focus on a toggle:

  • Enter / Space — toggles the active state (or, in single / autoplay / scroll modes, opens if not already active).
  • Arrow Down / Right — move focus to the next toggle (wraps).
  • Arrow Up / Left — move focus to the previous toggle (wraps).
  • Home / End — jump focus to the first / last toggle.

Live demo — FAQ accordion (default mode)

Section titled “Live demo — FAQ accordion (default mode)”

aa-tabs (no value = default mode), aa-duration="0.5" for the height tween. Each <p> runs aa-animate="text-slide-up" with aa-split="words mask", aa-stagger="0.04", aa-duration="0.6" — words slide up on tab-active via trigger inference, reverse on tab-inactive.

An attribute-driven scroll-animation library used in Webflow and Next.js projects. v8 is rebuilt on Vite + TypeScript with a tiny core and lazy-loaded features.

No. GSAP is a peer dependency — load it externally via a script tag (Webflow) or npm install (Next.js). The lib detects window.gsap and warns clearly if a required plugin is missing.

Yes — features like aa-tabs and aa-slider are interactive components with sensible defaults. You only need GSAP plugins for the modes that actually require them.

Live demo — single-mode (one always open)

Section titled “Live demo — single-mode (one always open)”

aa-tabs="single" with aa-duration="0.4". The first row’s toggle has aa-tabs-initial so it opens at init; clicking the active toggle is a no-op (single mode forbids closing the only open one).

Single mode auto-opens the first toggle (or the one marked aa-tabs-initial) and refuses to close the active one — clicking it is a no-op.

Opening a different toggle closes the previously active one, so exactly one panel is always visible.

Or any UI where the user should always see one section’s content. The state never disappears entirely.

Live demo — tab interface (visual-only, cross-fade)

Section titled “Live demo — tab interface (visual-only, cross-fade)”

When toggles only have visuals (no content), the lib applies role="tab" + role="tabpanel". Visuals are stacked in a relatively-positioned container — only the active one is fully opaque.

aa-tabs="single" with aa-duration="0.5". Each toggle pairs with an aa-tabs-visual (no aa-tabs-content) — that’s what flips the lib into tab semantics: tablist on the parent, tab/tabpanel on the children. Headlines inside each panel use aa-animate="text-fade-up" + aa-split="lines mask".

Design without compromise

Pixel-perfect mockups, motion specs, and component libraries that translate directly into production code.

Develop on solid primitives

Strict TypeScript, idiomatic GSAP, and a tiny core. Every animation is testable and reversible.

Ship to production with confidence

Playwright MCP visual verification, bundle-size CI gates, and migration-friendly lifecycle hooks for page transitions.

Live demo — accordion + side-panel visual

Section titled “Live demo — accordion + side-panel visual”

Combine content with a side-panel visual: the toggle expands its inline content and swaps the cross-fade panel. The lib detects content + visual and uses accordion ARIA semantics (button + region); the visual gets aria-hidden toggled.

aa-tabs="single" with aa-duration="0.45". Each row pairs an aa-tabs-content with an aa-tabs-visual sharing the same toggle id. The whole visual card runs aa-animate="fade-left" (fades + slides in from the left when the tab activates). Inside, the heading runs aa-animate="text-fade" with aa-split="chars" and aa-stagger="0.02" so characters fade in cascade once the card lands. Text stays black — lime accents the active border, never the body text.

Inline detail copy expands beneath the toggle while the side panel cross-fades to the matching visual.

Each row’s content runs aa-trigger inference automatically — inner aa-animate elements play on tab-active.

The visual on the right reverses smoothly when you switch tabs — no flash, no late layout reflow.

Discover the brief

Iterate on the shape

Ship to production

Live demo — autoplay with hover-pause (pause-on-interaction)

Section titled “Live demo — autoplay with hover-pause (pause-on-interaction)”

aa-autoplay="3 hover-pause" cycles every 3 seconds, fills the per-toggle width progress during the dwell, and pauses on mouse hover.

aa-autoplay="3 hover-pause" (per-tab dwell + hover behaviour), aa-duration="0.4" (height + cross-fade). Each toggle holds an aa-tabs-progress="width" element with aa-ease="none" for a linear fill. Hover pause is guarded by (hover: hover) so it stays out of the way on touch devices. Click while hovering jumps to that tab and stays paused — mouseleave then starts a fresh cycle from there.

Capture

Discover the moments worth animating.

Compose

Wire triggers to behaviours; let the lib pick the right primitive.

Curate

Tune duration, ease, and stagger across responsive breakpoints.

Live demo — autoplay (no hover, click-to-restart)

Section titled “Live demo — autoplay (no hover, click-to-restart)”

Same shape as the previous demo, but without the hover-pause flag. Cycling never pauses on hover. Clicking a tab restarts the cycle from that index — useful when you want a continuously-cycling carousel that responds to nudges instead of stopping on interaction.

aa-autoplay="3" (no hover-pause flag), aa-duration="0.4". Same aa-tabs-progress="width" element per toggle.

Capture

Discover the moments worth animating.

Compose

Wire triggers to behaviours; let the lib pick the right primitive.

Curate

Tune duration, ease, and stagger across responsive breakpoints.

Live demo — scroll-driven tabs with pinned section + segment progress

Section titled “Live demo — scroll-driven tabs with pinned section + segment progress”

aa-tabs="scroll" pins the section, then scrolling scrubs through the tabs. Each toggle has a width-style progress bar that fills during its segment of the pin range.

aa-distance="50" (vh per tab — three tabs = 150vh total pin range), aa-scroll-start="center center", aa-duration="0.5" for the visual cross-fade. Per-step aa-tabs-progress="width" bars fill proportionally within their segment. Clicking a step jumps to that section’s scroll position. On touch devices, the mode automatically falls back to single since pin-scrubbing is brittle there.

Discover the problem

Workshops, audits, ride-alongs — establish the constraints before sketching solutions.

Define the target

One paragraph of intent, one diagram of architecture. Everything else is a detail.

Deliver in slices

Ship the first slice in week one. Iterate against real usage instead of imagined requirements.

Live demo — accordion with image + animated text inside content

Section titled “Live demo — accordion with image + animated text inside content”

The expanding content carries an image and a paragraph; both pick up the trigger inference automatically. The image runs aa-animate="rotate-up-bl-ccw" (rotate counter-clockwise around the bottom-left corner with an upward offset). The paragraph runs aa-animate="text-fade" with aa-split="lines-chars" and aa-stagger="0.01 0.1" — characters fade in at 0.01s each within each line, lines stagger by 0.1s.

aa-tabs (default mode), aa-duration="0.5". Closing a tab fully resets the inner timelines so the next open starts afresh from the FROM state.

Sustained landscape compositions reward patience — the longer you sit with the frame, the more the subtle differences between shadow and light become legible to the lens.

Vertical lines repeat at irregular intervals, which the eye reads as rhythm rather than pattern. The right exposure preserves both the highlights at the canopy and the texture of the bark.

Fog flattens the tonal range, so the strongest images lean on graphic shape rather than detail. Wait for a moment when one element separates clearly from the grey, and let everything else recede.