Media · Carousel

Carousel

Horizontally or vertically scrollable rail of slides with keyboard, button, indicator, drag and autoplay navigation. One slide per page. Pattern follows the WAI-ARIA carousel pattern with role="region" + aria-roledescription="carousel"; indicators map 1:1 with slides as a tablist.

parts{compiled.parts.order.length} events{events.length} sizes5 apg carousel ↗ index {value}
{#snippet children(snippet: CarouselProviderSnippetProps)} {(api = snippet.api, isPlaying = snippet.isPlaying, '')} {#each slides as slide, i (slide.label)}
{slide.label} {slide.sub}
{/each}
{#each slides as _, i (i)} {/each} {/snippet}
trace {#if trace.length === 0} arrow keys · drag · click indicators {:else} {#each trace.slice(0, 3) as entry (entry.at)} {entry.event} · {entry.family}{entry.intent ? ' · ' + entry.intent : ''} {fmtTime(entry.at)} {/each} {/if} index {value}
{#if tab === 'live'}

Controls

Soma owns the index, autoplay timer, drag gesture, keyboard nav and orientation. Eidos adds chrome (variant / size / color) and renders the prev/next triggers as eidos <Button iconOnly variant="ghost"> via the canonical Button consumer pattern.

soma layout
soma drag
soma autoplay
soma imperative API exposed via the api snippet prop — drive the carousel from external buttons
isPlaying = {isPlaying}
eidos visual props
eidos composition · with bind:value svelte
{eidosSnippet}
{/if} {#if tab === 'api'}

API reference

8 parts: Provider, Viewport, ItemGroup, Item, PrevTrigger, NextTrigger, IndicatorGroup, Indicator.

Provider (Carousel)
PropTypeDefaultDescription
value bindablenumber0Current slide index (0-based).
defaultValuenumber0Initial slide index when uncontrolled.
onValueChange(i: number) => void—Called on every index change.
orientation'horizontal' | 'vertical''horizontal'Scroll axis.
slidesPerViewnumber1Slides visible at once (each Item basis = 100% / slidesPerView).
slidesToScrollnumber1Slides advanced per next() / prev().
align'start' | 'center' | 'end''start'Alignment of active slide(s) within viewport.
gapstring (CSS length)'0px'Inline-axis gap between slides.
loopbooleanfalseWrap at edges (also wraps autoplay).
dragEnabledbooleantrueAllow pointer drag to scrub.
dragThresholdnumber (0–1)0.25Release fraction past which next slide commits.
autoplaybooleanfalseEnable timer-driven advance.
autoplayDelaynumber (ms)4000Interval in milliseconds.
playOnInitbooleantrueStart the autoplay timer on mount.
pauseOnHoverbooleantruePause autoplay while hovered.
stopOnInteractionbooleanfalseLatch-stop after user clicks prev/next/indicator or drags.
stopOnFocusInbooleantruePause while focus is inside root.
stopOnMouseEnterbooleanfalsePause while cursor over root (Embla parity).
stopOnLastSnapbooleanfalseLatch-stop when last snap reached (loop=false only).
disabledbooleanfalseDisable all navigation.
aria-labelstring—Override accessible name (falls back to lang ref).
variant eidosControlVariant: 'surface' | 'outline' | 'ghost''surface'Container chrome.
size eidos'xs' | 'sm' | 'md' | 'lg' | 'xl''md'Cascades to indicator + trigger size.
color eidosColorRole'primary'Indicator + focus accent.
block eidosbooleantrueStretch root inline-size to container.
verticalBlockSize eidosstring (CSS length)'16rem'Viewport block-size in vertical orientation.
Viewport · ItemGroup

Viewport is the overflow: hidden clipping box. ItemGroup is the translatable strip whose transform the provider drives. Both are pass-through — no eidos-specific props.

Item
PropTypeDefaultDescription
indexnumberrequired0-based slide index. Used for aria-roledescription + indicator wiring.
childrenSnippet<[{`{ index, isActive }`}]>—Slide body. Receives active flag for per-slide effects.
PrevTrigger · NextTrigger
PropTypeDefaultDescription
variant eidosControlVariant: 'surface' | 'outline' | 'ghost''ghost'Button variant.
size eidos'xs' | 'sm' | 'md' | 'lg' | 'xl'cascades from CarouselButton size override.
rounded eidosButtonRounded'full'Corner radius.
intent eidosIntent—Evaluative intent.
color eidosColorRole—Hierarchical color (under intent='neutral').
iconOnly eidosbooleantrueSquare aspect, label sr-only.
icon eidosSnippetchevronOverride default arrow glyph.
child somaSnippet—Bypass Button entirely; render custom element.
IndicatorGroup · Indicator
PropTypeDefaultDescription
index (Indicator)numberrequired0-based slide this indicator navigates to.
aria-label (Indicator)string—Override accessible name; falls back to "Go to slide {`{n}`}".
children (Indicator)Snippet<[{`{ index, isActive }`}]>—Custom indicator body (default is a dot).
{/if} {#if tab === 'morfo'}

morfo · declarative contract

Source: src/uix/morfo/components/carousel.ts.

FieldValue
name"{carouselMorfo.name}"
kebab"{carouselMorfo.kebab}"
apg{carouselMorfo.apg}
parts.length{carouselMorfo.parts.length}
events.length{events.length}
Parts
{#each partsList as part (part.kebab)} {/each}
PartMarkerElementRoleArchetypeStatesOptional
{part.kebab} [{part.marker}] <{part.defaultElement}> {part.role ?? '—'} {part.archetype ?? '—'} {part.states.length ? part.states.join(' | ') : '—'} {part.optional ? 'yes' : 'no'}
Events declaration
{#each events as action (action.name)} {@const sem = action.semantic} {@const intentDecl = 'intent' in sem ? sem.intent : undefined} {@const intentStr = typeof intentDecl === 'string' ? intentDecl : intentDecl ? `fromProp:${(intentDecl as { fromProp?: string }).fromProp ?? '?'}` : '—'} {/each}
namefamilyverbsequenceintenttarget
{action.name} {sem.family} {sem.verb ?? '—'} {sem.sequence ?? 'pre'} {intentStr} {action.target}
{/if} {#if tab === 'sema'}

sema · events

{#each events as action (action.name)} {@const intentDecl = 'intent' in action.semantic ? action.semantic.intent : undefined} {@const effectiveIntent = typeof intentDecl === 'string' ? intentDecl : undefined} {/each}
NameFamilyVerbSequenceIntentPlay
{action.name} {action.semantic.family} {action.semantic.verb ?? '—'} {action.semantic.sequence ?? 'pre'} {effectiveIntent ?? '—'}
{/if} {#if tab === 'recipe'}

Eidos recipe

Selectors at src/uix/eidos/components/carousel/carousel.css.

SelectorSourceWhat it paints
[data-carousel]eidosRoot — flex column, gap, font.
[data-carousel][data-variant]eidosSurface / outline / ghost chrome around the rail.
[data-carousel][data-size]eidosIndicator dot diameter + gap.
[data-carousel][data-color]eidosActive-indicator accent + focus ring.
[data-carousel-viewport]morfoClipping box (overflow hidden, inline soma styles).
[data-carousel-item-group][data-dragging]morfoTransform transition off while dragging.
[data-carousel-item]eidosSlide — 100% basis along axis.
[data-carousel-prev-trigger] · [data-carousel-next-trigger]eidosAbsolute-positioned over viewport edges; vertical flips to top/bottom.
[data-carousel-indicator][data-state='active']eidosActive indicator: accent fill + 2.25× inline-size stretch.
{/if} {#if tab === 'a11y'}

Accessibility

Follows the APG carousel pattern. The root is role="region" with aria-roledescription="carousel" + an accessible name. Each slide is role="group" with aria-roledescription="slide". Indicators form a role="tablist" with role="tab" children.

Keyboard
KeyAction
ArrowRight / ArrowDownNext slide.
ArrowLeft / ArrowUpPrevious slide.
HomeFirst slide.
EndLast slide.
TabMove focus through prev / next / indicators / inner slide controls.
pointer dragScrub between slides; commits when past dragThreshold.
ARIA contract
PartAttributeValue
providerrole"region"
provideraria-roledescription"carousel"
provideraria-labellocalized "Carousel"
itemrole"group"
itemaria-roledescription"slide"
prev-trigger / next-triggertype / aria-label"button" / "Previous slide" | "Next slide"
indicator-grouprole / aria-orientation"tablist" / orientation
indicatorrole / aria-selected"tab" / "true|false"
{/if}