Carousels

mandatory

Horizontally scrolling stack of items, optimized for browsing a collection at a glance. Material parity (`/components/carousels`). Covers Hero, Multi-browse, and Contained layouts; snap behavior; swipe gestures; accessibility for sequential item navigation.

Spec — Carousels

Facet *isual*of Koder Design. Material parity: https://m3.material.io/components/carousels.

3 layout patterns

Layout Item count visible Use
*ero* 1 large + 1 preview edge Featured / spotlight ("Featured project")
*ulti-browse* 3-5 mixed sizes Browse-heavy ("Recent files")
*ontained* 2-4 equal sizes Card-like grid in a strip

Pick by intent: Hero for "look at THIS"; Multi-browse for "scan across"; Contained for "uniform set you can scroll through".

Anatomy (Hero layout)

←─                                                      →─
   ┌──────────────────────────────────┐  ┌─────┐
   │                                  │  │     │
   │       Featured item              │  │ next│
   │      (full width minus           │  │ peek│
   │       16 px edge for peek)       │  │     │
   │                                  │  │     │
   └──────────────────────────────────┘  └─────┘
        ●  ○  ○  ○  ○                       ← page indicator
  • *tem radius* 12 px (matches card defaults)
  • *tem gap* 8 px
  • *dge peek* 16 px (shows ~24 px of next/previous item)
  • *age indicator* dots, 8 px circles, 4 px gap
  • *nap* itemedge to containeredge

R1 — Multi-browse layout

Mixed item widths in a single row. Material reference: large, medium, small repeating pattern.

Slot Width Position
Large 200 dp Center / start
Medium 140 dp Trailing large
Small 80 dp At edges, indicates more

Items resize as they scroll across the visible area: focused = large, adjacent = medium, edges = small. Animation per scroll position.

R2 — Contained layout

Fixed item width, all equal. Width inferred from container:

Container width Items visible Item width
Compact (< 600 dp) 1.2 ~80% of container
Medium (600-839 dp) 2.2 ~45%
Expanded (≥ 840 dp) 3.2 ~30%

The .2 partial visible signals scrollability.

R3 — Snap behavior

  • *nap to item* scroll-end snaps to nearest item edge
  • *elocity threshold* fast flick passes through multiple items;

    slow drag snaps to nearest

  • *isable snap* optout via prop when freescroll is preferred

    (rare; document why)

  • Snap uses motion.kmd emphasized-decelerate easing (~350 ms)

R4 — Page indicator

Dots below carousel show:

  • Total item count (one dot per item, or grouped per "page" if > 10)
  • Current focus position (filled dot)
Item count Indicator
≤ 10 One dot per item
11-30 Grouped per visible page (e.g., 5 pages of 3 items)
> 30 "1 / 47" text indicator instead of dots

Tappable dots: tap dot scrolls to that item (motion-medium, ~300 ms).

R5 — Navigation controls

Surface Controls
Mobile Swipe + dots (no arrow buttons)
Tablet portrait Swipe + dots
Desktop Arrow buttons (◂ ▸) at edges + dots + keyboard

Arrow buttons appear on hover (default) OR always visible (opt-in). Disabled-state arrow when at start / end (38% opacity).

R6 — Keyboard navigation

When carousel has focus:

  • Arrow Left / Right: scroll to previous / next item (animated)
  • Home / End: jump to first / last
  • Tab: focus moves to focused item; Tab again moves OUT of carousel

    to the next focusable element after the carousel

  • Enter / Space on item: triggers item's action (open, etc.)

R7 — Item content

Items are normally cards or images. Each item must have:

  • A defined aspect ratio (16:9 / 4:3 / 1:1 — pick one per carousel)
  • A focusable target (whole item is the target by default)
  • An accessible name (aria-label or visible heading)
  • Loading state: skeleton matching item shape until content loads

R8 — Accessibility

  • Container: role="region" + aria-roledescription="carousel" +

    aria-label describing collection

  • Items: role="group" + aria-roledescription="slide" +

    aria-label="3 of 7" (position + total)

  • Page indicator: role="tablist"; each dot role="tab" with

    aria-selected="true" on current

  • Live region: when item changes via auto-advance, announce new

    item's name (polite live region)

  • Autoadvance ALWAYS pauses on hover, focus, or `prefersreduced-motion`

R9 — Autoadvance (optin)

Off by default. When enabled:

  • Interval: 5-8 seconds per item (configurable; never < 4 s)
  • Pause on hover / focus / touch-down
  • Resume only after user interaction settles
  • Always pair with manual controls (dots + swipe)
  • Disabled when prefers-reduced-motion: reduce

Auto-advance is for promotional / passive carousels only. Don't auto-advance functional content (settings, lists).

R10 — Animation

  • *tem scroll* 1:1 with finger drag; momentum on release
  • *nap settle* motionemphasizeddecelerate (~350 ms)
  • *rrow click* scroll by one item, motion-medium (~250 ms)
  • *ulti-browse resize* items grow / shrink as they cross focus

    position (continuous interpolation)

  • Reduced motion: no resize animation, no auto-advance, instant snap

R11 — Per-preset variation

Preset Visual
material3 12 px corners, edge peek, multisize in Multibrowse
material2 4 px corners, uniform sizes, no peek by default
ios_cupertino Page dots only (no arrows even on desktop), flush items
gnome Adwaita carousel with side fade, integrated arrow buttons
windows_11 Wide layout with arrow chevrons at sides, no dot indicator
brutalist Sharp corners, no peek, hard snap (no smooth scroll)
terminal_classic Sequential [1/7] indicator + Arrow Left/Right navigation only

R12 — Forbidden patterns

  • ❌ Carousels with > 30 items (use grid / list instead)
  • ❌ Carousels as primary navigation (use tabs or nav components)
  • ❌ Hiding the page indicator entirely when count > 1 (user can't

    see "there's more")

  • ❌ Auto-advance < 4 s (too fast to read)
  • ❌ Auto-advance for forms or settings content
  • ❌ Carousel inside a horizontally-scrolling parent (nested

    horizontal scroll = unusable)

  • ❌ Carousel without keyboard navigation
  • ❌ Carousel without prefers-reduced-motion handling
  • ❌ Items with variable aspect ratios (jarring resize on scroll)
  • themes/motion.kmd — emphasized-decelerate easing for snap
  • themes/shape.kmd — item corner radius
  • themes/color-roles.kmd — indicator dot color
  • components/cards.kmd — item content pattern
  • app-layout/window-size-classes.kmd — Contained items-visible rules
  • foundations/elements.kmd — Container + Navigator families

Source: ../home/koder/dev/koder/meta/docs/stack/specs/components/carousels.kmd