Slotted slides at discrete positions, with buttons, keyboard, swipe and indicators sharing one selection.
<tp-carousel>
Carousel
Default density
Compact density
Restart from the initial state
Show the copyable source
Single sign-onSAML and OIDC are now available on every plan.
Audit logEvery permission change, kept for 400 days.
Faster exportsLarge CSV exports finish up to 5× sooner.
Scheduled reportsEmail a dashboard to your team every Monday.
Single sign-onSAML and OIDC are now available on every plan.
Audit logEvery permission change, kept for 400 days.
Faster exportsLarge CSV exports finish up to 5× sooner.
Scheduled reportsEmail a dashboard to your team every Monday.
<tp-carousel label="What's new in 2.4" style="max-inline-size:32rem;margin-inline:auto">
<div style="display:grid;align-content:end;gap:var(--tp-space-1);aspect-ratio:16/9;padding:var(--tp-space-6);border-radius:var(--tp-radius-lg);background:var(--tp-muted)">
<strong style="font-size:var(--tp-text-xl)">Single sign-on</strong>
<span style="color:var(--tp-muted-foreground)">SAML and OIDC are now available on every plan.</span>
</div>
<div style="display:grid;align-content:end;gap:var(--tp-space-1);aspect-ratio:16/9;padding:var(--tp-space-6);border-radius:var(--tp-radius-lg);background:var(--tp-muted)">
<strong style="font-size:var(--tp-text-xl)">Audit log</strong>
<span style="color:var(--tp-muted-foreground)">Every permission change, kept for 400 days.</span>
</div>
<div style="display:grid;align-content:end;gap:var(--tp-space-1);aspect-ratio:16/9;padding:var(--tp-space-6);border-radius:var(--tp-radius-lg);background:var(--tp-muted)">
<strong style="font-size:var(--tp-text-xl)">Faster exports</strong>
<span style="color:var(--tp-muted-foreground)">Large CSV exports finish up to 5× sooner.</span>
</div>
<div style="display:grid;align-content:end;gap:var(--tp-space-1);aspect-ratio:16/9;padding:var(--tp-space-6);border-radius:var(--tp-radius-lg);background:var(--tp-muted)">
<strong style="font-size:var(--tp-text-xl)">Scheduled reports</strong>
<span style="color:var(--tp-muted-foreground)">Email a dashboard to your team every Monday.</span>
</div>
</tp-carousel>
Usage
Each child of tp-carousel is one slide. Previous and next Buttons, arrow keys, swipe and the indicators all move the same numeric selection, and the current position is announced politely. Always give the Carousel a label, and size it through CSS: it fills the inline size it is given. Transitions never block interaction by default. A drag can grab a moving carousel and continues from the presented position.
Several per view
Layout, navigation and indicator settings live in the options property. layout.itemsPerView accepts fractions, so a partly visible next slide hints that there is more; items-per-movement sets how far each step moves. Resolution order is defaults, base options, root scalar properties, then the matching breakpoint. breakpoints maps nonnegative pixel widths or positive @ratio keys to responsive overrides.
Items per view
Default density
Compact density
Restart from the initial state
Show the copyable source
AtlasDesign system · 12 membersBilling v2Payments · 5 membersOnboardingGrowth · 8 membersMobile appiOS and Android · 9 membersSearchPlatform · 4 membersStatus pageInfrastructure · 3 members
AtlasDesign system · 12 membersBilling v2Payments · 5 membersOnboardingGrowth · 8 membersMobile appiOS and Android · 9 membersSearchPlatform · 4 membersStatus pageInfrastructure · 3 members
indicators.type is fraction by default, or bullets and progress. Set clickable: true to make bullets real Buttons. navigation.placement puts the arrows in the footer, inside the viewport or outside it. navigation.hideOnClick and indicators.hideOnClick toggle their controls when noninteractive slide content is clicked. Indicator count is snap count, which may differ from item count or mounted virtual count.
Bullets inside
Default density
Compact density
Restart from the initial state
Show the copyable source
“We replaced three internal tools in a week.”
“Release reviews went from an hour to ten minutes.”
“Our designers and engineers finally share one source.”
“We replaced three internal tools in a week.”
“Release reviews went from an hour to ten minutes.”
“Our designers and engineers finally share one source.”
<tp-carousel id="carousel-bullets" label="Customer stories" style="max-inline-size:32rem;margin-inline:auto">
<blockquote style="margin:0;display:grid;align-content:center;gap:var(--tp-space-3);min-block-size:12rem;padding:var(--tp-space-8) var(--tp-space-12);border-radius:var(--tp-radius-lg);background:var(--tp-muted)">
<p style="margin:0;font-size:var(--tp-text-lg)">“We replaced three internal tools in a week.”</p>
<footer style="color:var(--tp-muted-foreground)">Priya Raman, Head of Ops at Northwind</footer>
</blockquote>
<blockquote style="margin:0;display:grid;align-content:center;gap:var(--tp-space-3);min-block-size:12rem;padding:var(--tp-space-8) var(--tp-space-12);border-radius:var(--tp-radius-lg);background:var(--tp-muted)">
<p style="margin:0;font-size:var(--tp-text-lg)">“Release reviews went from an hour to ten minutes.”</p>
<footer style="color:var(--tp-muted-foreground)">Daniel Okafor, Engineering Manager at Lumen</footer>
</blockquote>
<blockquote style="margin:0;display:grid;align-content:center;gap:var(--tp-space-3);min-block-size:12rem;padding:var(--tp-space-8) var(--tp-space-12);border-radius:var(--tp-radius-lg);background:var(--tp-muted)">
<p style="margin:0;font-size:var(--tp-text-lg)">“Our designers and engineers finally share one source.”</p>
<footer style="color:var(--tp-muted-foreground)">Mara Lindqvist, Design Lead at Fjord</footer>
</blockquote>
</tp-carousel>
<script type="module">
await customElements.whenDefined('tp-carousel');
document.getElementById('carousel-bullets').options = {
navigation: { placement: 'inside' },
indicators: { type: 'bullets' },
};
</script>
Loop and autoplay
loop lets navigation wrap past either end. autoplay sets an interval in milliseconds and adds a Pause/Resume Button before the slides; hover, focus, reduced motion and hidden tabs pause the timer automatically. Root loop permits loopMode='continuous' (default) or 'rewind'. Continuous mode permutes owned shells without cloning consumer controls or changing logical source order.
Autoplay
Default density
Compact density
Restart from the initial state
Show the copyable source
Press ⌘K anywhereJump to any project, page or setting.
Invite your teamProjects are more useful with reviewers.
Connect GitHubLink pull requests to tasks automatically.
Press ⌘K anywhereJump to any project, page or setting.
Invite your teamProjects are more useful with reviewers.
Connect GitHubLink pull requests to tasks automatically.
<tp-carousel label="Getting started tips" loop autoplay="5000" style="max-inline-size:28rem;margin-inline:auto">
<div style="display:grid;place-content:center;text-align:center;gap:var(--tp-space-1);min-block-size:9rem;padding:var(--tp-space-6);border-radius:var(--tp-radius-lg);background:var(--tp-muted)">
<strong>Press ⌘K anywhere</strong>
<span style="color:var(--tp-muted-foreground)">Jump to any project, page or setting.</span>
</div>
<div style="display:grid;place-content:center;text-align:center;gap:var(--tp-space-1);min-block-size:9rem;padding:var(--tp-space-6);border-radius:var(--tp-radius-lg);background:var(--tp-muted)">
<strong>Invite your team</strong>
<span style="color:var(--tp-muted-foreground)">Projects are more useful with reviewers.</span>
</div>
<div style="display:grid;place-content:center;text-align:center;gap:var(--tp-space-1);min-block-size:9rem;padding:var(--tp-space-6);border-radius:var(--tp-radius-lg);background:var(--tp-muted)">
<strong>Connect GitHub</strong>
<span style="color:var(--tp-muted-foreground)">Link pull requests to tasks automatically.</span>
</div>
</tp-carousel>
Use autoplay sparingly: moving content is hard to read and easy to miss. Prefer it for decorative or repeated content, never for the only path to information.
Vertical
orientation="vertical" moves the track, keys and swipe to the block axis. A vertical Carousel needs a definite block size.
Vertical
Default density
Compact density
Restart from the initial state
Show the copyable source
Design reviewTue, Oct 13 · 10:00Sprint planningWed, Oct 14 · 14:30Customer call: LumenThu, Oct 15 · 09:00Release 2.5 go/no-goFri, Oct 16 · 16:00
Design reviewTue, Oct 13 · 10:00Sprint planningWed, Oct 14 · 14:30Customer call: LumenThu, Oct 15 · 09:00Release 2.5 go/no-goFri, Oct 16 · 16:00
By default the track slides between items. The effect property swaps in a richer transition, built by a factory you import, so pages without one bundle none. carouselCrossfadeEffect() fades between slides, carouselLayeredEffect() turns media away with perspective, carouselParallaxEffect() moves media more slowly than its slide, carouselFocusEffect() dims and shrinks slides away from the aligned one, and carouselShaderEffect() runs a WebGL transition: wipe, displace, chromatic, crosswarp or glass.
Stack effects keep every item at the viewport origin and force one item per view
and per movement. Conflicting layout options are overridden with a tp-diagnostic.
Track effects keep the moving track, so any layout, loop or centering works. With several items in view, set interaction.navigateOnItemClick so a click on a partly visible item brings it into view.
Effects need the transform transport. With transport: 'scroll' the effect is
ignored and a diagnostic reports it.
Crossfade
Default density
Compact density
Restart from the initial state
Show the copyable source
Single sign-onSAML and OIDC are now available on every plan.
Audit logEvery permission change, kept for 400 days.
Faster exportsLarge CSV exports finish up to 5× sooner.
Single sign-onSAML and OIDC are now available on every plan.
Audit logEvery permission change, kept for 400 days.
Faster exportsLarge CSV exports finish up to 5× sooner.
<tp-carousel id="carousel-crossfade" label="Release highlights" loop style="max-inline-size:32rem;margin-inline:auto">
<div style="display:grid;align-content:end;gap:var(--tp-space-1);aspect-ratio:16/9;padding:var(--tp-space-6);border-radius:var(--tp-radius-lg);background:var(--tp-muted)">
<strong data-carousel-layer="1" style="font-size:var(--tp-text-xl)">Single sign-on</strong>
<span data-carousel-layer="2" style="color:var(--tp-muted-foreground)">SAML and OIDC are now available on every plan.</span>
</div>
<div style="display:grid;align-content:end;gap:var(--tp-space-1);aspect-ratio:16/9;padding:var(--tp-space-6);border-radius:var(--tp-radius-lg);background:var(--tp-foreground);color:var(--tp-background)">
<strong data-carousel-layer="1" style="font-size:var(--tp-text-xl)">Audit log</strong>
<span data-carousel-layer="2" style="opacity:.7">Every permission change, kept for 400 days.</span>
</div>
<div style="display:grid;align-content:end;gap:var(--tp-space-1);aspect-ratio:16/9;padding:var(--tp-space-6);border-radius:var(--tp-radius-lg);background:var(--tp-muted)">
<strong data-carousel-layer="1" style="font-size:var(--tp-text-xl)">Faster exports</strong>
<span data-carousel-layer="2" style="color:var(--tp-muted-foreground)">Large CSV exports finish up to 5× sooner.</span>
</div>
</tp-carousel>
<script type="module">
import { carouselCrossfadeEffect } from '@tweakpad/ui';
await customElements.whenDefined('tp-carousel');
document.getElementById('carousel-crossfade').effect = carouselCrossfadeEffect({ duration: 600 });
</script>
Mark the one image or video per slide that an effect transforms with data-carousel-media, and text that should reveal in order with data-carousel-layer="1", "2" and so on. Effects follow dragging, play backwards when a navigation reverses and settle instantly under reduced motion; slide semantics and announcements don’t change.
Layered 3D
Default density
Compact density
Restart from the initial state
Show the copyable source
Hill countryThree days by car · from $420Alpine lakesHut to hut · from $610Old town by bikeGuided weekend · from $290
Hill countryThree days by car · from $420Alpine lakesHut to hut · from $610Old town by bikeGuided weekend · from $290
<tp-carousel id="carousel-layered" label="Weekend escapes" style="max-inline-size:36rem;margin-inline:auto">
<figure style="position:relative;margin:0;aspect-ratio:16/9;overflow:hidden;border-radius:var(--tp-radius-lg);background:var(--tp-muted)">
<img data-carousel-media src="https://images.unsplash.com/photo-1500530855697-b586d89ba3ee?w=1200&q=80" alt="A winding road through green hills at sunset" style="display:block;inline-size:100%;block-size:100%;object-fit:cover;color:transparent">
<figcaption style="position:absolute;inset-inline-start:var(--tp-space-6);inset-block-end:var(--tp-space-6);display:grid;gap:var(--tp-space-1);color:white;text-shadow:0 1px 12px rgb(0 0 0 / 55%)">
<strong data-carousel-layer="1" style="font-size:var(--tp-text-2xl)">Hill country</strong>
<span data-carousel-layer="2" style="font-size:var(--tp-text-sm)">Three days by car · from $420</span>
</figcaption>
</figure>
<figure style="position:relative;margin:0;aspect-ratio:16/9;overflow:hidden;border-radius:var(--tp-radius-lg);background:var(--tp-muted)">
<img data-carousel-media src="https://images.unsplash.com/photo-1501785888041-af3ef285b470?w=1200&q=80" alt="Mountain lake at dawn" style="display:block;inline-size:100%;block-size:100%;object-fit:cover;color:transparent">
<figcaption style="position:absolute;inset-inline-start:var(--tp-space-6);inset-block-end:var(--tp-space-6);display:grid;gap:var(--tp-space-1);color:white;text-shadow:0 1px 12px rgb(0 0 0 / 55%)">
<strong data-carousel-layer="1" style="font-size:var(--tp-text-2xl)">Alpine lakes</strong>
<span data-carousel-layer="2" style="font-size:var(--tp-text-sm)">Hut to hut · from $610</span>
</figcaption>
</figure>
<figure style="position:relative;margin:0;aspect-ratio:16/9;overflow:hidden;border-radius:var(--tp-radius-lg);background:var(--tp-muted)">
<img data-carousel-media src="https://images.unsplash.com/photo-1465869185982-5a1a7522cbcb?w=1200&q=80" alt="A bicycle in a wet alley" style="display:block;inline-size:100%;block-size:100%;object-fit:cover;color:transparent">
<figcaption style="position:absolute;inset-inline-start:var(--tp-space-6);inset-block-end:var(--tp-space-6);display:grid;gap:var(--tp-space-1);color:white;text-shadow:0 1px 12px rgb(0 0 0 / 55%)">
<strong data-carousel-layer="1" style="font-size:var(--tp-text-2xl)">Old town by bike</strong>
<span data-carousel-layer="2" style="font-size:var(--tp-text-sm)">Guided weekend · from $290</span>
</figcaption>
</figure>
</tp-carousel>
<script type="module">
import { carouselLayeredEffect } from '@tweakpad/ui';
await customElements.whenDefined('tp-carousel');
document.getElementById('carousel-layered').effect = carouselLayeredEffect();
</script>
The Layered effect turns the outgoing photo away in perspective, turns the next one in, then raises its caption layers in order. Every step follows a drag, so readers can scrub through it.
Parallax
Default density
Compact density
Restart from the initial state
Show the copyable source
Parallax keeps the sliding track, so it works with several slides in view, centering and loop. Each photo travels more slowly than its clipped slide. With neighbors peeking in, interaction.navigateOnItemClick lets a click on a partly visible slide bring it into view.
Focus
Default density
Compact density
Restart from the initial state
Show the copyable source
Focus dims and shrinks cards as they move away from the center and reveals the centered card’s caption as it arrives, which suits a row of destinations or products.
Shader transition
Default density
Compact density
Restart from the initial state
Show the copyable source
The shader effect draws a WebGL2 transition between the two data-carousel-media images: wipe sweeps a noise-edged front, displace and chromatic flow a noise map through the frame, crosswarp zooms and morphs across, and glass sweeps a refracting ridge. direction takes an edge or a corner. Captions stay live text above the canvas. Images need crossorigin="anonymous" from a CORS-enabled host; without WebGL2, loaded media or motion, the effect falls back to a crossfade. All shader carousels on a page share one WebGL2 context. Each carousel displays frames in its own canvas, so many carousels never exhaust the browser’s context limit.
Items from data
Virtual archive with controlled selection
Default density
Compact density
Restart from the initial state
Show the copyable source
Set items to an array and renderItem to a Lit template to build slides from data instead of children. virtual keeps only the slides near the viewport in the DOM, so a long archive stays light. Setting value makes selection controlled: each proposal waits for your listener to assign it, which is where a router or store would step in. Do not combine default slide children with data items. Leaving the controlled value unchanged also rejects it. Do not switch between controlled and uncontrolled mode during one host lifetime.
Events
tp-value-change proposes a new position and can be canceled; tp-value-commit follows once it is accepted. Read the committed position from index, or drive the Carousel through its controller.
Commit
Default density
Compact density
Restart from the initial state
Show the copyable source
const result = await carousel.controller?.scrollToId('review', { speed: 0 });
// {status, index, id, reason}: the actual committed destination
const unsubscribe = carousel.controller?.subscribe((snapshot) => console.log(snapshot));
unsubscribe?.();
Navigation resolves accepted, unchanged, rejected, or cancelled, with actual committed index/id and a reason code. User rejection does not reject the Promise.
Keyboard
Arrow keys follow the active axis and writing direction; Home and End jump to the first and last slide. Keys owned by a control inside a slide, such as a text field or slider, stay with that control. Optional PageUp/PageDown use the same snap navigation. An inner Carousel owns its own navigation.
Custom effects
CarouselEffect objects have:
name and layout (track or stack);
optional duration, easing, easingRole (standard, enter or exit, the motion
token used without easing) and constrain(input, diagnose);
attach(context), which returns { frame(frame), update?(), detach() }.
Each frame provides:
phase (drag, animate, settle), position, velocity (items per second), direction;
items, each with progress, shell and content;
current, next and amount, the pair being transitioned and how far.
The context provides the viewport, track, effect surface, orientation, direction,
invalidate() and diagnose(). Restore everything you change in detach(). Helpers
stackEffectConstraint, carouselItemProgress and mergeCarouselOptions are exported.
Grid/multirow layout, unsnapped free momentum, zoom, linked controllers, thumbnail
controllers, URL routing, raw HTML renderers and a public plugin/module injection
system are not supported. Effects are typed presentation objects,
not installable modules.
API Reference
Every element also accepts motion-policy.
Attributes
AttributePropertyTypeDefaultDescriptionindexindexnumber—The committed position; setting it navigates there.looploopbooleanfalseLets navigation wrap past either end.items-per-movementitemsPerMovementnumber1How many slides one step of navigation moves.autoplayautoplaynumber0The interval between automatic advances, in milliseconds; zero turns autoplay off. Autoplay adds a pause control and pauses on hover, focus, reduced motion and hidden tabs.labellabelstring''The accessible name of the carousel, such as "Customer stories".
Properties
PropertyTypeDefaultDescriptionvaluenumber | undefined—The selected position; set it before the carousel initializes to control selection, then publish each accepted proposal back.defaultValuenumber | undefined—The position shown first, when the carousel owns its selection.itemsreadonly T[] | undefined—Builds the slides from data instead of children; any array, even an empty one, switches to data.getItemId((item: T, index: number) => CarouselId) | undefined—The stable identity of a data item, which keeps the selection when items reorder; defaults to the item itself or its id.getItemLabel((item: T, index: number) => string) | undefined—The name of a data item's slide, announced with its position.getItemOptions((item: T, index: number) => CarouselItemOptions) | undefined—Per-item behavior for a data item: disabled, its own autoplay delay, or a label.renderItem((item: T, context: CarouselRenderContext) => unknown) | undefined—Renders a data item's slide, with its id, index and whether it is selected or visible.optionsCarouselOptions{}Layout, interaction, navigation, indicators, scrollbar, wheel, keyboard, autoplay, virtualization and breakpoint settings, merged over the defaults.onControllerChange((controller: CarouselController | null) => void) | undefined—Called with the carousel's controller once it initializes, and with null when it is released.effectCarouselEffect | nullnullA transition created by an effect factory, such as carouselCrossfadeEffect(); null keeps the sliding track. Effects transform each slide's element marked data-carousel-media and reveal data-carousel-layer elements in order.controllerread-onlyCarouselController | null—The handle for driving and observing the carousel from code (scroll to a slide, read its snapshot, control autoplay); null before it initializes.indicatorTyperead-onlystring—The indicator style in effect, from options.indicators.controlsPlacementread-onlystring—Where the previous and next buttons sit, from options.navigation.
Events
EventTypeDescriptiontp-value-changeTpValueChangeEvent<number>Cancelable proposal of a new position before it commits; a controlled carousel must publish the accepted value.tp-value-commitTpValueCommitEvent<number>After a new position commits, with the position and the previous one.
Slots
SlotDescriptiondefaultThe slides, one per child.previousReplaces the built-in previous button.nextReplaces the built-in next button.indicatorsReplaces the built-in indicators.
CSS parts
PartDescription::part(carousel)The root region holding the viewport and its controls.::part(carousel-announcements)The polite live region announcing the position.::part(carousel-autoplay-control)The pause and resume button autoplay adds.::part(carousel-controls)The row of controls, placed in the footer, inside or outside the viewport.::part(carousel-effect-surface)The layer an effect draws on in place of the moving track.::part(carousel-indicator)The indicators: a fraction, bullets or a progress bar.::part(carousel-item)The shell around one slide.::part(carousel-scrollbar)The optional scrollbar track.::part(carousel-status)The visible position text.::part(carousel-thumb)The thumb of the scrollbar.::part(carousel-track)The moving strip of slides.::part(carousel-viewport)The clipping window the slides move through.