メインコンテンツへスキップ
menumenu tanreiexpand_morecontrasttranslate
tanreiexpand_more
はじめにWebReactSwiftUIJetpack ComposeFlutterReact Nativeプリセット自分のデザインシステムを作るAI エージェントと Tanrei を使うブランドデザイントークンデザインシステムとカスタマイズTheme Studioカラータイポグラフィアイコン形状エレベーションモーションインタラクション状態振る舞いレイアウトアクセシビリティ言葉カラートークンタイプスケールトークン形状トークンエレベーショントークンモーショントークン状態トークンスペースと寸法のトークンコンポーネントボタンメインアクションボタンセグメントボタンボタングループメニュー付きボタンツールバーコピーボタンチェックボックスラジオボタンスイッチチップメニュースライダースライダーフィールドカレンダーDateTimePickerフィールドグループラジオグループチェックボックスグループセレクトセレクトフィールドメニューバーファイルアップロードファイルピッカー 上級者向けドロップゾーンテキスト入力欄 上級者向けテキストフィールドチップフィールドフィールドコンボボックス 上級者向けコンボボックスフィールド数値入力欄 上級者向け数値フィールドカラー入力欄 上級者向けカラーフィールドワンタイムコード入力欄 上級者向けワンタイムコードフィールド埋め込みテキスト入力欄 上級者向けチャットコンポーザーカード区切り線モーダルレイヤー 上級者向けダイアログリストリスト行ボトムシートサイドシートカルーセルコードブロックテーブルポップオーバーステップアコーディオン説明リスト図アプリシェルページヘッダーツリービュータイムライン画像プレビューハンドルサイズ変更できる枠スクロールエリアバッジプログレスインジケーターメーターナンバーティッカーローディングインジケータースナックバーツールチップキーボードショートカットタグインラインコードコールアウトアバター空の状態タググループスケルトンファイル項目色見本マーキーテキストタブタブビューアプリバーナビゲーションバーナビゲーションドロワー検索リンクページネーション前後のリンクステッパーパンくずリスト目次リンクカードFAB メニューステータスTanrei の作り方コントリビューションライセンスと帰属
ホームコンポーネントカルーセル

カルーセル

フォーカスのある 1 つの項目を中心に横へスクロールするビジュアルの項目の列で、各項目はそれが示すものを開きます。目玉の列は、ループし、位置をドットで示し、自動で進むこともできます。

content_copyMarkdown で表示
カテゴリ
コンテインメント
トークン
comp.carousel
レベル
基礎

概要

Web
tanrei-carousel
schedule
SwiftUI
TanreiCarousel
schedule
Jetpack Compose
TanreiCarousel
schedule
Flutter
TanreiCarousel
schedule
React Native
Carousel
schedule

生成元 docs/status/status.json, specs/carousel.spec.json

仕様は改訂済み、各プラットフォームは追従待ち

このページは、仕様の現在の形のカルーセルを説明しています。layout プロパティは、独立した選択(ヒーローの位置、幅が流動的か固定か、縮小、止まる位置、ループ)に置き換わりました。目玉の列では、ドットを表示したり自動で進めたりできます。ヒーローは既定で中央に置かれ、隣り合う項目の間隔はスクロールのどの位置でも一定です。Web コンポーネントはすでにこのすべてに対応しており、デモもそれを示しています。SwiftUI、Compose、Flutter、React Native は上の表で「追従待ち」です。追従するまでは以前の版(layout があり、ループ・ドット・自動再生・前と次のコントロールはない)を提供しています。ヒーローは既定で中央とlayout からの移行も参照してください。

既定:中央にヒーロー、その隣に小さい項目

landscapelocation_cityforestwaterterrainkayaking

使い方

カルーセルが持つ項目は item の 1 種類です。受け取り方は、繰り返しの子要素を受け取るときのプラットフォームの流儀に従います。Web では子要素の <tanrei-carousel-item>、SwiftUI ではビュービルダーの中の TanreiCarouselItem のビュー、Compose では項目のラムダから呼ぶ TanreiCarouselItem、Flutter では children に並べた TanreiCarouselItem のウィジェット、React Native では子要素の CarouselItem です。

各項目は 1 つのアクションです。画像などの利用者側のコンテンツを持ち、人が実行すると click を発火します(プラットフォームに応じて action、onClick、onTap、onPress)。項目の label がアクセシブルネームです。「海沿いの道、写真 12 枚」のように、コンテンツが何を示しているかを書きます。コンテンツそのものは支援技術から隠し、その中に独自のコントロールを置いてはいけません。

列は、フォーカスのある 1 つの項目(ヒーロー)を中心に並びます。並べ方はいくつかの独立したプロパティで決まり、どれも既定値は眺めるための列に合うものです。ヒーローは中央にあり、その隣の項目は狭まり、列は項目の切れ目で止まります。itemWidth はヒーローの幅で、指定しなければデザインシステムの既定の幅になります。目玉の列には、loop、ドット(indicator)、autoplay を加えられます。autoplay がなければ、カルーセルには制御する値がありません。人がスクロールするかフォーカスを動かしたときにだけスクロールします。

列の両端の上には「前へ」と「次へ」のコントロールがあり、列を 1 項目ずつ動かします。既定では、マウスやトラックパッドのときに描かれ、ポインタがカルーセルの上にあるか、フォーカスがその中にある間に現れます。製品の言語で previousLabel と nextLabel を付けてください。前と次のコントロールを参照してください。

Web と React の例は現在の仕様に沿っています。SwiftUI、Compose、Flutter、React Native の例は、これらのプラットフォームがいま提供している以前の版のものです。layout(既定は複数の項目を並べる形、ほかに hero と uncontained)と itemWidth を持ち、ループ、ドット、自動再生はありません。

<tanrei-carousel previous-label="前の散歩道" next-label="次の散歩道" label="近くの散歩道">
  <tanrei-carousel-item label="海沿いの道、写真 12 枚"><img src="coast.jpg" alt="" /></tanrei-carousel-item>
  <tanrei-carousel-item label="旧市街、写真 8 枚"><img src="town.jpg" alt="" /></tanrei-carousel-item>
  <tanrei-carousel-item label="森の周回路、写真 20 枚"><img src="forest.jpg" alt="" /></tanrei-carousel-item>
</tanrei-carousel>

<!-- 目玉の列:ループし、ドットを表示し、自動で進みます。ヒーローは既定で中央です。 -->
<tanrei-carousel previous-label="前の散歩道" next-label="次の散歩道" label="おすすめの散歩道" loop indicator autoplay
                 play-label="スライドショーを再生" pause-label="スライドショーを一時停止">…</tanrei-carousel>

<!-- 先頭側の端から並ぶ素朴な列:どの項目も幅 120(CSS ピクセルの数値)で、スクロールが終わった場所で止まります。
     fixed-width と free-scroll は、既定が true の fluidWidth と snap の逆です。 -->
<tanrei-carousel previous-label="前の散歩道" next-label="次の散歩道" label="最近表示した項目" hero-alignment="start" fixed-width free-scroll item-width="120">…</tanrei-carousel>

<script type="module">
  import '@tanrei/web/components/carousel';
  for (const item of document.querySelectorAll('tanrei-carousel-item')) {
    item.addEventListener('click', () => openWalk(item.label));
  }
  // 発火するのは再生コントロールだけです。保留や、paused を設定したときには発火しません。
  document.querySelector('[label="おすすめの散歩道"]').addEventListener('pausedchange', (e) => savePaused(e.detail.paused));
</script>
import { Carousel, CarouselItem } from '@tanrei/react';

<Carousel label="最近表示した項目" itemWidth={120} fluidWidth={false} snap={false}>
  {walks.map((walk) => (
    <CarouselItem key={walk.id} label={walk.label} onClick={() => openWalk(walk)}>
      <img src={walk.image} alt="" />
    </CarouselItem>
  ))}
</Carousel>

<Carousel label="おすすめの散歩道" loop indicator autoplay paused={paused}
  onPausedChange={(e) => setPaused(e.detail.paused)}
  previousLabel="前の散歩道" nextLabel="次の散歩道"
  playLabel="スライドショーを再生" pauseLabel="スライドショーを一時停止">
  {featured.map((walk) => <CarouselItem key={walk.id} label={walk.label} onClick={() => openWalk(walk)}><img src={walk.image} alt="" /></CarouselItem>)}
</Carousel>
import Tanrei

// 以前の版:並べ方は layout で選びます。.hero は目玉の項目を 1 つずつ見せます。
TanreiCarousel("おすすめの散歩道", layout: .hero) {
    ForEach(walks) { walk in
        TanreiCarouselItem(walk.label, action: { open(walk) }) {
            WalkImage(walk)
        }
    }
}
import androidx.compose.ui.unit.dp
import io.github.chnotchy.tanrei.components.TanreiCarousel
import io.github.chnotchy.tanrei.components.TanreiCarouselItem
import io.github.chnotchy.tanrei.components.TanreiCarouselLayout

// 以前の版。Material のカルーセルと同じく項目のラムダを受け取ります。itemWidth は Dp で、null ならトークンの値です。
// Uncontained は素朴な列です。どの項目も同じ幅で、端で切れます。
TanreiCarousel(
    itemCount = walks.size,
    contentDescription = "最近表示した項目",
    layout = TanreiCarouselLayout.Uncontained,
    itemWidth = 120.dp,
) { index ->
    val walk = walks[index]
    TanreiCarouselItem(label = walk.label, onClick = { open(walk) }) {
        WalkImage(walk)
    }
}
import 'package:tanrei/tanrei.dart';

// 以前の版:並べ方は layout で選びます。
TanreiCarousel(
  semanticLabel: 'おすすめの散歩道',
  layout: TanreiCarouselLayout.hero,
  children: [
    for (final Walk walk in walks)
      TanreiCarouselItem(label: walk.label, onTap: () => open(walk), child: WalkImage(walk)),
  ],
),
import { Image } from 'react-native';
import { Carousel, CarouselItem } from '@tanrei/react-native';

// 以前の版の既定のレイアウト(複数の項目を並べる形)です。
<Carousel accessibilityLabel="近くの散歩道">
  {walks.map((walk) => (
    <CarouselItem key={walk.id} label={walk.label} onPress={() => openWalk(walk)}>
      <Image source={walk.image} style={{ flex: 1 }} />
    </CarouselItem>
  ))}
</Carousel>

仕様

取れる最大の幅のヒーローと、先頭側の端から自由にスクロールする固定幅の列

landscapelocation_cityforestwaterlandscapelocation_cityforestwaterterrain

列の並べ方:固定幅、縮小、ループ、ヒーロー寄りのコンテンツ

固定幅で、スクロールが終わった位置に止まる:fixed-width free-scroll
landscapelocation_cityforestwaterterrain
ヒーロー以外を小さく描き、端のない列:fixed-width scale-others loop
landscapelocation_cityforestwaterterrain
端のない列。両側がいつも埋まります:loop
landscapelocation_cityforestwaterterrain
狭くなった項目はヒーローに近い側を見せる:item-width=“120” content-alignment=“toward-hero” loop
landscapelocation_cityforestwaterterrainkayaking

ドット、自動再生、進み具合、一時停止、再生コントロールだけ

ドット:indicator
landscapelocation_cityforestwaterterrainkayaking
ドットと再生コントロール付きの自動再生:indicator autoplay
landscapelocation_cityforestwaterterrainkayaking
次の項目までの時間を現在のドットが示す:autoplay-progress
landscapelocation_cityforestwaterterrainkayaking
利用者またはプロダクトが一時停止した状態:paused
landscapelocation_cityforestwaterterrainkayaking
ドットのない自動再生:再生コントロールだけ
landscapelocation_cityforestwater

動きを減らす設定がオンの環境では、自動再生は動きません。その場合、自動再生のデモは止まったままで、再生コントロールと進み具合も描かれません。

構成要素

プロパティ

プロパティ 型 既定値 説明
heroAlignment start | center "center" Where the item in focus (the hero) sits: in the middle, with items on both sides (the default), or at the inner start edge (the left in a left-to-right language), with items after it only. Not a token axis: geometry, read from the same tokens.
itemWidth number null The width the hero is laid out at, in the platform's unit (CSS px, pt, dp, logical pixels); null takes comp.carousel.container.large-width. With fixed widths every item is this wide (at most the inner width); with fluid widths the hero is kept between a floor and a cap that leave room for the others (CRL-9). Choose it from the content's shape: a product's posters and thumbnails differ.
fluidWidth boolean true Whether item widths change fluidly: true narrows the items beside the hero (on both sides of a hero in the centre) to a medium and a small width, each width following the scroll; false makes every item the hero's width, the ones that do not fit cut by the carousel's edges.
contentAlignment center | toward-hero "center" With fluid widths, where a narrowed item's content is anchored: center shows its middle; toward-hero aligns the items after the hero to their start and the items before it to their end, so each shows the side nearest the hero. Ignored while fluidWidth is false.
scaleOthers boolean false Whether the items other than the hero are drawn smaller, down to comp.carousel.container.min-scale, the scale following the scroll and the gap between drawn items staying the same. It applies with fluid or fixed widths.
snap boolean true Whether the row settles at an item when a scroll ends; false lets it rest wherever the scroll ends.
loop boolean false Whether the row is endless: scrolling past the last item continues to the first, and back. Takes effect only with two or more items; with fewer it is ignored.
navigation auto | always | none "auto" Whether the previous and next controls are drawn over the ends of the row (CRL-41, CRL-42). auto draws them where the primary pointer is fine and can hover — a mouse or a trackpad, where no finger swipes the row — and shows them while the pointer is over the carousel or focus is within it; always draws them on every device and shows them at all times; none draws neither. Keep auto wherever you can: without the controls a person with a mouse moves the row only with Shift and the wheel, or with the indicator where it is drawn, and never with a single press (WCAG 2.5.7).
indicator boolean false Whether the dots are drawn below the items: one per resting position, the current one stretched. For a short featured row; a long row to browse needs none.
autoplay boolean false Whether the row advances by itself, one item every autoplayInterval. It holds while the carousel is hovered, has focus within, is pressed or is hidden, and never runs where motion is reduced (CRL-32 to CRL-34).
autoplayInterval number null How long the row rests at an item before autoplay moves on, in milliseconds; null takes comp.carousel.autoplay.interval. Give text-heavy items more time than images.
paused boolean false Whether autoplay is paused, by the person (with the playback control) or by the product. The playback control changes it and emits pausedChange; a product that sets it emits nothing.
playbackControl boolean true Whether the playback control is drawn while autoplay is on. A product may hide it, but should keep it wherever it can: on touch there is no hover, and without it a person cannot stop the motion (WCAG 2.2.2).
autoplayProgress boolean false Whether the current dot fills to show the time until autoplay moves on. Needs indicator; ignored while autoplay is off or paused.
playLabel string "" The playback control's accessible name while autoplay is paused ("Play the slideshow"). Required while the control can be drawn.
pauseLabel string "" The playback control's accessible name while autoplay is playing ("Pause the slideshow"). Required while the control can be drawn.
previousLabel string "" The previous control's accessible name ("Previous walk"). Required while the control can be drawn.
nextLabel string "" The next control's accessible name ("Next walk"). Required while the control can be drawn.
label string "" Names the carousel's list, and its indicator, for assistive technology ("Recently played"). Required.

イベント

イベント 説明
pausedChange The person paused or played autoplay with the playback control; carries the new paused value. Not emitted when the product sets paused, nor for the holds of CRL-33.

アイテム

このコンポーネントが繰り返すアイテムの種類です。フィクスチャはアイテムを順に並べ、各プラットフォームはそれぞれの流儀で種類ごとに名前を付けます。

item

One item of the row: the product's content in a rounded box, one action named by its label.

プロパティ 型 既定値 説明
label string "" The item's accessible name: what its content shows ("Coast path, 12 photos"). Required; the content itself is not read. It is also the indicator's value text while the item is in focus.
スロット 説明
default The item's content. A fixture item's content is the name of a glyph: the harness draws it centred in the content box (the hero's width, the row's height) at sys.icon.medium through the icon helper, with the family and fill of an unselected icon, in the item's content color, on the item's container color.
イベント 説明
click The item was activated by pointer, touch, keyboard or assistive technology.
仕様 Web SwiftUI Jetpack Compose Flutter React Native
アイテム tanrei-carousel-item TanreiCarouselItem TanreiCarouselItem TanreiCarouselItem CarouselItem
label · プロパティ label label label label label
click · イベント click action onClick onTap onPress
備考 A <tanrei-carousel-item> child; its content is its default slot. TanreiCarouselItem(_ label: String, action: @escaping () -> Void) { content }. TanreiCarouselItem(label, onClick) { content }, called from the carousel's item lambda. TanreiCarouselItem(label:, onTap:, child:), one of the carousel's children. <CarouselItem label onPress>{content}</CarouselItem>, a child of Carousel.

状態

振る舞い

EARS 記法による要件です。各プラットフォームの実装は、この ID に対応するテストで検証されます。要件の原文は仕様ファイルのものをそのまま掲載しています。

ID 要件
CRL-1 WHEN the user activates an item by pointer, touch, Enter or Space THE carousel SHALL emit that item's click exactly once; a press that turns into a scroll (a drag beyond the platform's touch slop, a fling) SHALL NOT activate the item it started on.
CRL-2 WHILE an item, the indicator or the playback control is hovered, keyboard-focused or pressed THE carousel SHALL render that part's container, content color and opacity, and an item's shape, from the component tokens of its highest-precedence active state, with precedence pressed over focused over hovered; focus gained by pointer or touch SHALL NOT select the focused state.
CRL-3 WHEN an item, the indicator or the playback control receives keyboard focus THE carousel SHALL show the focus ring without animation, inside an item's container, which the row would otherwise clip, and around the indicator's or the playback control's container; focus gained by pointer or touch SHALL NOT show it.
CRL-4 THE carousel SHALL make each item's hit area its drawn container with half of comp.carousel.layout.gap on each side, which for a small item at comp.carousel.container.small-min-width is sys.target.touch wide with the default tokens, and SHALL NOT let the hit area of an item that fills a slot fall below sys.target.pointer wide for a fine pointer or sys.target.touch for touch; a system whose widths give less SHALL still extend the hit area without changing the drawn item, and the row's height SHALL NOT be below the target either. An item on its way between the last slot and an edge slot (CRL-12) is narrower than any slot: its hit area SHALL be its drawn container with half of the gap on each side and no wider, so that it never covers its neighbour's.
CRL-5 WHEN a carousel item or the playback control is pressed by pointer, touch, Enter or Space, or the indicator by pointer or touch, THE carousel SHALL show that part's pressed state without delay and SHALL keep it visible for at least sys.motion.duration.press-hold, even when the press ends sooner; the indicator's SHALL last as long as the press does.
CRL-6 WHILE a carousel item, the indicator or the playback control is pressed THE carousel SHALL answer as sys.behavior.pressed-feedback names: overlay applies the pressed tokens to the whole container at once; ripple grows the pressed state layer from the point of contact (from the center for keyboard activation); dim applies the pressed opacity to the whole control and draws no state layer; none draws no answer to the press. Under ripple and dim only the container color stays with the state under the press; content color, opacity and shape still come from the pressed tokens. Under none every part stays with the state under the press. The ripple grows over comp.carousel.motion.feedback-duration with comp.carousel.motion.state-easing, fades over comp.carousel.motion.state-duration with comp.carousel.motion.state-easing once the press has ended and press-hold has passed, and appears whole where motion is reduced.
CRL-7 THE carousel SHALL ease the corners of its items' containers, the indicator's container and the playback control by sys.shape.corner-smoothing (0 is a quarter circle, 0.6 the iOS continuous corner; a platform with only the OS curve draws it for any amount above 0) and show keyboard focus as sys.behavior.focus-style names (ring or glow); a platform that cannot draw a choice SHALL fall back to the first value and state that in its README.
CRL-8 THE carousel SHALL be as wide as its parent; its row of items SHALL be comp.carousel.layout.height tall unless the product sizes the carousel through the platform's own sizing, in which case the row takes that height less the controls row (CRL-28), and every item SHALL be as tall as the row. It SHALL lay its items out in one row in the order given, comp.carousel.layout.gap apart (g), inside comp.carousel.layout.padding-inline at its start and its end: the inner width W is the carousel's width less that padding twice. Items SHALL scroll under the padding to the carousel's edges and be clipped there.
CRL-9 THE carousel SHALL take L from itemWidth, or from comp.carousel.container.large-width while itemWidth is null, and SHALL make the hero slot H wide. WHILE fluidWidth is false H SHALL be L, at most W. WHILE fluidWidth is true H SHALL be L kept between a floor and a cap: WHILE heroAlignment is center, the floor (W − 4 × g − 2 × comp.carousel.container.small-max-width) / 3 and the cap W − 2 × (g + comp.carousel.container.small-max-width); WHILE heroAlignment is start, the floor (W − 2 × g − comp.carousel.container.small-max-width) / 2 and the cap W − g − comp.carousel.container.small-max-width. IF fluidWidth is true and that cap is less than comp.carousel.container.small-max-width THE carousel SHALL lay the row out as it does with fixed widths (CRL-13), H being W.
CRL-10 WHILE fluidWidth is true THE carousel SHALL fill the width E that the hero slot leaves on each side where items follow it (on both sides, E = (W − H) / 2 − g, WHILE heroAlignment is center; after it, E = W − H − g, WHILE heroAlignment is start) with slots g apart, going outward from the hero: IF E − g is less than 2 × comp.carousel.container.small-min-width, one small slot E wide; otherwise a medium slot M wide and then a small slot S wide, where S is (E − g) / 3 kept between comp.carousel.container.small-min-width and comp.carousel.container.small-max-width and M = E − g − S. IF M is then greater than H, M SHALL be H and S SHALL be E − g − H. Widths SHALL NOT be rounded before the items are placed; CRL-40 says where a platform that draws on whole device pixels puts the rounding.
CRL-11 THE carousel SHALL place the hero slot, where the item in focus sits, WHILE heroAlignment is center with its middle at the middle of the inner width (its start h at (W − H) / 2), and WHILE heroAlignment is start at the inner start edge (h is 0); and SHALL place the other slots of CRL-10, on both sides of it at the centre and after it at the start, each one g beyond the slot before it going outward, measured between the slots' drawn edges.
CRL-12 THE carousel SHALL place its items by a scroll position p, a number of items that advances by one for every H + g the row is scrolled. WHILE loop is not in effect (CRL-27) p SHALL run from 0 to N − 1 (N the number of items), except where CRL-13 ends the range sooner for a hero at the start; the resting positions are the whole numbers in the range and, where the range ends at a fraction, its end, D of them in all, so D is N wherever the range is not ended sooner. Item i, counted from 0, sits at slot position s = i − p. Slot 0 is the hero slot and slots 1, 2 and on follow it toward the end; WHILE heroAlignment is center slots −1, −2 and on precede it toward the start, and WHILE heroAlignment is start no slot precedes it (CRL-10, CRL-11, CRL-13). WHILE fluidWidth is true, beyond the last slot on each side lies an edge slot of no width, g beyond that slot — before a hero at the start, g before the hero slot itself — so that at a whole p nothing is drawn beyond the last slots; an item at or beyond an edge slot SHALL NOT be drawn. An item's start and width SHALL be the linear interpolation between those of the two slots around s. So at p = 0 the first item is the hero and nothing is drawn before it, and as p grows each item moves toward the start, growing from nothing at the edge slot to H as it reaches slot 0 and narrowing again to nothing as it leaves, a hero at the start narrowing against the inner start edge; at the end of the range the last item is the hero and the slots after it are empty.
CRL-13 WHILE fluidWidth is false THE carousel SHALL make every slot H wide, slot k starting at h + k × (H + g), h being the hero slot's start (CRL-11), with no edge slot: the slots go on beyond the carousel's edges, where the items are clipped (CRL-8), so that CRL-12's interpolation starts item i at h + s × (H + g). WHILE scaleOthers is also true the slots other than the hero's take the widths and the places CRL-26 gives them. WHILE heroAlignment is start, scaleOthers is false and loop is not in effect, the range of p SHALL end where the last item ends at the inner end edge, at the greater of 0 and (N × (H + g) − g − W) / (H + g), so that WHILE every item fits in W the row does not scroll.
CRL-14 WHEN the user scrolls the carousel by touch, pen, trackpad, a horizontal wheel or Shift with a vertical wheel THE carousel SHALL follow the scroll; WHILE snap is true, WHEN the scroll ends (a drag released, a wheel or trackpad gesture over) THE carousel SHALL settle at a resting position: the next one in the direction of a fling, or else the nearest one, over comp.carousel.motion.scroll-duration with comp.carousel.motion.scroll-easing, at once where motion is reduced; WHILE snap is false it SHALL come to rest wherever the scroll ends. A fling is a release faster than the platform's minimum fling velocity (Android's ViewConfiguration.getScaledMinimumFlingVelocity, Flutter's kMinFlingVelocity) or, where the platform names none (the web, SwiftUI, React Native), 0.5 points per millisecond. A release's velocity is the pointer's over its last moves: WHERE the pointer has not moved for longer than the time after which the platform's velocity tracker takes a pointer as stopped (40 milliseconds in Android's VelocityTracker, Jetpack Compose's VelocityTracker and Flutter's VelocityTracker) or, where the platform names none (the web, SwiftUI, React Native), 40 milliseconds before the release THE carousel SHALL take the release as having no velocity, so that a drag that stops where it is wanted and is let go there is no fling, however fast it moved before it stopped. A horizontal wheel, a trackpad and Shift with a vertical wheel are physical in every layout direction: THE carousel SHALL move the row the way the platform's own scrolling containers move for that input and SHALL NOT reverse it in a right-to-left layout, so that Shift with a wheel turned down, which brings into view what lies further to the right, moves toward later items in left-to-right and toward earlier items in right-to-left.
CRL-15 THE carousel SHALL lay each item's content out H wide and as tall as the row, and SHALL clip it to the item's container and its comp.carousel.container.shape corners; WHILE fluidWidth is false or contentAlignment is center the content SHALL be centred on the container, and WHILE fluidWidth is true and contentAlignment is toward-hero an item after the hero (s above 0) SHALL align the content's start edge with its container's start edge and an item before it (s below 0) the content's end edge with its container's end edge, so that a narrowed item shows the side of its content nearest the hero and the content glides inside the item as it widens and narrows.
CRL-16 THE carousel SHALL fill each item's container with comp.carousel.item.container.color at rest, under its content, whatever the item's state, and SHALL hand the content comp.carousel.item.content.color as the platform's inherited foreground (the web's inherited color, SwiftUI's foreground style, Compose's content color, Flutter's default text and icon style, React Native's Tanrei text context). Because an item's content usually covers it (an image), the carousel SHALL draw the state layer over the content: comp.carousel.item.state-layer.color at sys.state.hover-state-layer-opacity, sys.state.focus-state-layer-opacity or sys.state.pressed-state-layer-opacity for the item's highest-precedence state, and the ripple of CRL-6 in the same layer, so hover, focus and press show on any content; the container color under the content is the resting fill only.
CRL-17 THE carousel SHALL be at most five Tab stops, in this order: the playback control while it is drawn (CRL-35), so that the motion can be stopped before anything else is reached; the previous and then the next control while they are drawn (CRL-41), before the items, so that a person can move the row before reading it; the items, as one stop, at the item that last had keyboard focus in it or else at the resting item (the item at slot 0 while fluidWidth is true, heroAlignment is center or snap is true, and otherwise the first item wholly in view); and the indicator while it is drawn (CRL-29). WHILE an item has keyboard focus Right Arrow and Left Arrow SHALL move focus to the next and previous item, from the last to the first and the first to the last WHILE loop is in effect and otherwise without wrapping, and Home and End to the first and last item; in a right-to-left layout Left Arrow SHALL move to the next item and Right Arrow to the previous.
CRL-18 WHEN an item receives keyboard focus or assistive-technology focus THE carousel SHALL scroll, over comp.carousel.motion.scroll-duration with comp.carousel.motion.scroll-easing (at once where motion is reduced), so that WHILE fluidWidth is true, heroAlignment is center or snap is true the item sits at slot 0 or, where the range ends first, at the resting position nearest it, going the shorter way round WHILE loop is in effect; and otherwise so that the whole item is in view, scrolling as little as that needs.
CRL-19 THE carousel SHALL expose its items as a list named by label, and each item as one element with the button role, named by its label, with its position among the items and their number; it SHALL hide each item's content from assistive technology (the label names the item), and SHALL keep items that are out of view exposed, in order, so that a screen reader reaches every item (CRL-18 brings it into view). The indicator (CRL-31), the playback control (CRL-35) and the previous and next controls (CRL-41) SHALL be outside the list and SHALL NOT be counted among its items.
CRL-20 WHILE an item's label is empty THE carousel SHALL still render it, and in debug builds report that it has no accessible name; WHILE the carousel's label is empty it SHALL report that the list and the indicator have no name; WHILE autoplay and playbackControl are true and playLabel or pauseLabel is empty it SHALL report the missing name; WHILE the previous and next controls are drawn (CRL-41) and previousLabel or nextLabel is empty it SHALL report the missing name; and WHILE indicator is true and the dots do not fit (CRL-29) it SHALL report that they are not drawn. An item's content SHALL contain no interactive element; a platform that can inspect it (the web, on every slot change) SHALL report one in debug builds, and a platform that cannot SHALL state the rule in its README.
CRL-21 WHEN the carousel's width or its items change THE carousel SHALL arrange its slots again and keep the resting item (CRL-17) where it was, as far as the new range allows, without motion; WHEN the number of items falls below two it SHALL stop looping (CRL-27) and advancing (CRL-32), and no longer draw the indicator, the playback control or the previous and next controls.
CRL-22 WHEN the colors of an item, the indicator or the playback control change between states THE carousel SHALL animate them over comp.carousel.motion.state-duration with comp.carousel.motion.state-easing, and SHALL change them at once where motion is reduced.
CRL-23 WHEN the layout direction is right-to-left THE carousel SHALL mirror its row and its controls row: the inner start edge at the right, items moving toward the right as p grows, the padding, the scroll direction, the start and end sides of contentAlignment, the order of the dots, the playback control's side and the sides of the previous and next controls (the previous control at the right) all mirrored; it SHALL grow a ripple from the point of contact as in left-to-right, and SHALL NOT mirror an item's content or the pause and play_arrow glyphs. The chevron_left and chevron_right glyphs of the previous and next controls SHALL be drawn mirrored exactly when the system's icon set marks the name as mirrored, by the Button's own icon helper; THE carousel SHALL NOT mirror a glyph itself.
CRL-24 WHEN heroAlignment, contentAlignment or navigation names a value the carousel does not define THE carousel SHALL render with that prop's default value, and in debug builds report the unknown value; WHEN itemWidth or autoplayInterval is not a positive number THE carousel SHALL treat it as null and in debug builds report it.
CRL-25 WHILE the text size is enlarged THE carousel SHALL keep its height, slots and items as they are: it holds no text of its own, and an item's content follows the text size itself.
CRL-26 WHILE scaleOthers is true THE carousel SHALL give the hero slot the scale 1 and every other slot the scale comp.carousel.container.min-scale, and SHALL draw each item's container at the scale interpolated between the two slots around s, which is 1 − (1 − comp.carousel.container.min-scale) × the lesser of 1 and |s|: as tall as the row times that scale, centred on the row's height, with its content drawn at that scale. WHILE fluidWidth is false a slot SHALL also be H times its scale wide, and the slots SHALL be placed g apart from drawn edge to drawn edge going outward from the hero slot, so that the smaller items close up to their neighbours; WHILE fluidWidth is true the slots keep the widths of CRL-10, which already fill the row, and only the height and the content take the scale. The corners of a scaled container SHALL stay the radius of comp.carousel.container.shape (or of its state's shape token) and SHALL NOT be multiplied by the scale; the item's focus ring SHALL be drawn at its own width inside the drawn container, and the item's hit area (CRL-4) SHALL follow the drawn container. The scale SHALL follow the scroll where motion is reduced as well, because the person's own scroll drives it.
CRL-27 WHILE loop is true and there are at least two items THE carousel SHALL scroll without end in both directions: p SHALL take any value, every whole number SHALL be a resting position, and item i SHALL sit at the one slot position s that differs from i − p by a whole multiple of N and lies above −N / 2 and at most N / 2 WHILE heroAlignment is center, or above −1 and at most N − 1 WHILE heroAlignment is start, so that scrolling past the last item continues to the first and before the first to the last. Each item SHALL be drawn once: with fluid widths and enough items for every slot and both edge slots an item passes from one side to the other at the edge slots, where it has no width, and with fewer items, or with fixed widths and too few items to reach beyond both edges, a slot stays empty and an item that passes to the other side may appear there at once. WHILE there are fewer than two items THE carousel SHALL ignore loop; loop is in effect only while both conditions hold.
CRL-28 WHILE the indicator (CRL-29) or the playback control (CRL-35) is drawn THE carousel SHALL draw, directly below its row of items, a controls row as wide as the carousel and comp.carousel.controls.height tall that does not scroll, with the indicator's container centred on the carousel's width and the playback control at its inline end, its end edge at the inner end edge, both centred on the row's height; WHILE neither is drawn there SHALL be no controls row. The indicator SHALL stay centred whether or not the playback control is drawn.
CRL-29 WHILE indicator is true and there are at least two resting positions THE carousel SHALL draw the indicator: D dots in reading order, comp.carousel.dot.gap apart, each comp.carousel.dot.size tall with round ends, inside a container that reaches comp.carousel.controls.indicator-padding-inline beyond the first and last dot, is comp.carousel.controls.button-size tall and has comp.carousel.controls.shape corners. The indicator's position q follows p: between resting positions j and j + 1 it runs linearly from j to j + 1, and WHILE loop is in effect q is p less a whole multiple of N, from 0 up to N, its distance to a dot measured around the ring. Dot j SHALL be comp.carousel.dot.size + (comp.carousel.dot.active-width − comp.carousel.dot.size) × w wide, w being the greater of 0 and 1 − the distance from q to j, and SHALL be drawn in comp.carousel.dot.color with a fill of comp.carousel.indicator.content.color over it at opacity w, across its whole width unless CRL-36 says otherwise, so that the current dot is stretched and in the active color and the stretch and the color pass to the next dot as the row scrolls. IF the container would be wider than W − 2 × (sys.target.touch + g) THE carousel SHALL NOT draw the indicator.
CRL-30 WHEN the person presses the indicator by pointer or touch anywhere in its hit area THE carousel SHALL move the row to the resting position of the dot nearest the press and, while the press moves along the dots, to the dot nearest it, each move over comp.carousel.motion.scroll-duration with comp.carousel.motion.scroll-easing (at once where motion is reduced); WHILE the indicator has keyboard focus Right Arrow and Left Arrow SHALL move the row to the next and previous resting position (wrapping WHILE loop is in effect, and otherwise not), Home and End to the first and last, mirrored in a right-to-left layout; WHEN assistive technology increments or decrements the indicator it SHALL move by one resting position. Focus SHALL stay on the indicator, and the items' Tab stop (CRL-17) SHALL become the resting item the move brings.
CRL-31 THE carousel SHALL expose the indicator as one slider (the adjustable trait on Apple platforms), named by label, horizontal, with the lowest value 1, the highest D and the value of the resting position nearest q counted from 1, and SHALL give it as value text the label of the item that position brings to slot 0 or, where the range ends first, the resting item there; its dots SHALL NOT be exposed one by one.
CRL-32 WHILE autoplay is true, paused is false, there are at least two resting positions, motion is not reduced (CRL-34) and no hold of CRL-33 is in effect THE carousel SHALL advance to the next resting position each time autoplayInterval (or comp.carousel.autoplay.interval while it is null) has passed with the row at rest, scrolling over comp.carousel.motion.autoplay-duration with comp.carousel.motion.autoplay-easing; from the last resting position it SHALL go on to the first, forward WHILE loop is in effect and otherwise back across the row over the same duration. An advance SHALL NOT move focus, change the items' Tab stop, or make an announcement.
CRL-33 WHILE a pointer is over the carousel (its row or its controls row), focus is anywhere within it however it came there, the person is pressing or dragging the row or the indicator, or the platform reports the carousel's window or document hidden, THE carousel SHALL NOT advance and SHALL hold the time that has passed; WHEN the last hold ends it SHALL continue from the time it held. WHEN the person moves the row (a scroll, a key, the indicator, the previous or the next control) the time SHALL start again from zero once the row comes to rest. WHEN the person activates the playback control to play, the hover and focus holds in effect at that moment SHALL end, and each SHALL apply again only once the pointer or focus has left the carousel and entered it again.
CRL-34 WHILE motion is reduced THE carousel SHALL NOT advance by itself, whatever autoplay and paused say, and SHALL NOT draw the playback control or the autoplay progress; WHEN motion stops being reduced it SHALL start autoplay again (paused permitting) with the time from zero. Where motion is reduced the width, the scale and the dots SHALL still follow a scroll the person makes.
CRL-35 WHILE autoplay is true, playbackControl is true, there are at least two resting positions and motion is not reduced THE carousel SHALL draw the playback control in the controls row: an icon-only button, a comp.carousel.controls.button-size square with comp.carousel.controls.shape corners, filled with comp.carousel.playback.container.color, holding the icon set's pause glyph while paused is false and play_arrow while paused is true, comp.carousel.controls.icon.size, in comp.carousel.playback.content.color, with the family and fill of an unselected icon, sys.icon.weight and its optical size clamped to sys.icon.optical-size.min and sys.icon.optical-size.max. It SHALL be exposed with the button role, named by pauseLabel while paused is false and by playLabel while paused is true, with no pressed or selected state. WHEN the person activates it THE carousel SHALL toggle paused, emit pausedChange with the new value and keep focus on it, and on play the time SHALL start from zero; WHEN the product sets paused THE carousel SHALL emit nothing.
CRL-36 WHILE autoplayProgress is true, the indicator is drawn, autoplay is true, paused is false and motion is not reduced THE carousel SHALL draw the fill of CRL-29 only on the dot of the resting position the row last came to rest at, from that dot's start edge across the part of its width that the time passed (CRL-33) is of the interval, growing with the time and stopping while a hold is in effect, and on no other dot, so that the dot stands in comp.carousel.dot.color and fills with comp.carousel.indicator.content.color until the row moves on, the fill fading as the dot narrows.
CRL-37 THE carousel SHALL give the playback control a hit area centred on it of at least sys.target.touch for touch and sys.target.pointer for a fine pointer, and the indicator a hit area as tall as the controls row and as wide as its container, at least sys.target.touch wide; with the default tokens both fit inside the controls row, and CRL-29 keeps them apart.
CRL-38 WHILE the icon set's font has not loaded THE carousel SHALL keep the playback control's glyph box and draw nothing in it, never the glyph's name as text.
CRL-39 THE carousel SHALL take its layout direction from where it is used — the direction of the nearest ancestor that sets one, and the platform's own where none does — and SHALL have no direction setting of its own; WHEN that direction changes while the carousel is shown THE carousel SHALL draw itself for the new direction at once, without a motion of its own, keeping the item at rest (the same item in reading order, not the same scroll offset), its playback and its focus.
CRL-40 THE carousel SHALL keep the distance from the drawn end edge of an item's container to the drawn start edge of the next item's container equal to comp.carousel.layout.gap, for every two neighbouring items that are both drawn, at every scroll position: at rest and at every moment of a drag, a fling, a settle, an advance and a move by the keys or the dots, in every combination of heroAlignment, fluidWidth, scaleOthers, contentAlignment, snap and loop, and in both layout directions. The edges are those of the container as it is drawn, after any scale (CRL-26), not those of a box laid out before the scale. THE carousel SHALL place every item it draws in a frame from the same scroll position p, and SHALL NOT ease, delay or animate an item's start, width or scale apart from p. A platform that draws on whole device pixels SHALL round each item's start edge and take its end edge from the next item's rounded start edge less the gap as that platform rounds it, so that the rounding falls on an item's width and never on a gap, and no two gaps in view differ.
CRL-41 WHILE navigation is auto or always and there are at least two resting positions THE carousel SHALL draw a previous control over the inline start of its row and a next control over its inline end, above the items: the previous control's start edge at the inner start edge and the next control's end edge at the inner end edge, both centred on the row's height. They SHALL be icon-only Buttons of the medium size in the variant comp.carousel.navigation.button.variant names, given through the channel a Button reads from the component that places it and otherwise unrestyled, showing the icon set's chevron_left and chevron_right glyphs and named by previousLabel and nextLabel. WHILE navigation is auto THE carousel SHALL draw them only where the primary pointer is fine and can hover; WHILE navigation is none, or there are fewer than two resting positions, it SHALL draw neither.
CRL-42 WHILE navigation is auto THE carousel SHALL show the previous and next controls only while a pointer is over the carousel (its row or its controls row) or focus is within it, however it came there, fading them in and out over comp.carousel.motion.state-duration with comp.carousel.motion.state-easing, at once where motion is reduced; hidden, they SHALL keep their place in the Tab order (CRL-17) and stay exposed to assistive technology with their names and states, so that a keyboard reaches them and focus shows them. WHILE navigation is always THE carousel SHALL show them at all times.
CRL-43 WHEN the person activates the previous or the next control THE carousel SHALL move the row to the resting position before or after the one it is at — while it is moving, the one it is moving to; between two resting positions, the nearest one before or after where it is — over comp.carousel.motion.scroll-duration with comp.carousel.motion.scroll-easing (at once where motion is reduced), from the first to the last and from the last to the first WHILE loop is in effect. Focus SHALL stay on the control, and the items' Tab stop (CRL-17) SHALL become the resting item the move brings. WHILE loop is not in effect THE carousel SHALL disable the previous control while the row is at, or moving to, the first resting position, and the next control at the last: drawn and exposed as disabled and refusing activation, but kept in the Tab order and keeping focus, so that the stops do not change as the row moves and focus is never lost when a control reaches its end.

プラットフォーム別の名前対応

パリティは API が同一であることを意味しません。同じ契約を、各プラットフォームの流儀で表現します。

仕様 Web SwiftUI Jetpack Compose Flutter React Native
コンポーネント tanrei-carousel TanreiCarousel TanreiCarousel TanreiCarousel Carousel
heroAlignment · プロパティ heroAlignment heroAlignment heroAlignment heroAlignment heroAlignment
itemWidth · プロパティ itemWidth itemWidth itemWidth itemWidth itemWidth
fluidWidth · プロパティ fluidWidth fluidWidth fluidWidth fluidWidth fluidWidth
contentAlignment · プロパティ contentAlignment contentAlignment contentAlignment contentAlignment contentAlignment
scaleOthers · プロパティ scaleOthers scaleOthers scaleOthers scaleOthers scaleOthers
snap · プロパティ snap snap snap snap snap
loop · プロパティ loop loop loop loop loop
navigation · プロパティ navigation navigation navigation navigation navigation
indicator · プロパティ indicator showsIndicator indicator indicator indicator
autoplay · プロパティ autoplay autoplay autoplay autoplay autoplay
autoplayInterval · プロパティ autoplayInterval autoplayInterval autoplayInterval autoplayInterval autoplayInterval
paused · プロパティ paused isPaused paused paused paused
playbackControl · プロパティ playbackControl showsPlaybackControl playbackControl playbackControl playbackControl
autoplayProgress · プロパティ autoplayProgress showsAutoplayProgress autoplayProgress autoplayProgress autoplayProgress
playLabel · プロパティ playLabel playLabel playLabel playLabel playLabel
pauseLabel · プロパティ pauseLabel pauseLabel pauseLabel pauseLabel pauseLabel
previousLabel · プロパティ previousLabel previousLabel previousLabel previousLabel previousLabel
nextLabel · プロパティ nextLabel nextLabel nextLabel nextLabel nextLabel
label · プロパティ label label contentDescription semanticLabel accessibilityLabel
pausedChange · イベント pausedchange isPaused onPausedChange onPausedChanged onPausedChange
備考 Attributes: hero-alignment, item-width (a number of CSS pixels; without it the property is null), content-alignment, scale-others, loop, indicator, autoplay, autoplay-interval (milliseconds), paused, autoplay-progress, play-label, pause-label, navigation (reflected), previous-label, next-label. fluidWidth, snap and playbackControl are boolean properties that default to true; their attributes are the inverses fixed-width, free-scroll and no-playback-control (an HTML boolean attribute cannot default to true). pausedchange is a CustomEvent whose detail.paused is the new value. Built on a native horizontal scroller (so wheel, trackpad and touch scroll as the platform does) whose track is as long as the scroll range; the items are placed from its scroll offset (CRL-12), and the settle animates the scroll offset over comp.carousel.motion.scroll-duration and scroll-easing once the scroll ends (scrollend, or 100 ms without a scroll event, because not every engine sends scrollend after a wheel), since a smooth scrollTo cannot follow the tokens. Fling 0.5 px/ms, measured from the finger's last touch moves over their own time stamps; after a fling the settle starts at the release, and the browser's momentum is held until it ends. While loop is in effect the track is several turns long and the offset is moved by a whole number of turns (N × (H + g)) when the row rests near either end, which draws nothing new. Autoplay reads prefers-reduced-motion and document.visibilityState when it would advance and listens for their changes; the hover hold is pointerenter / pointerleave on the host, the focus hold focusin / focusout. The items are placed from one scroll position in one pass, each start rounded to a device pixel and each end taken from the next start (CRL-40). The previous and next controls are <tanrei-button> elements with focusable-when-disabled, given their variant through the Button's group-item channel; auto draws them under (hover: hover) and (pointer: fine) and shows them while the host matches :hover or :focus-within. Snapshot harness: a 400 × 200 stage (the carousel fills it: W 368), or 400 × 248 for the fixtures with a controls row, on a surface page, scrolled to its start, the pointer outside, motion not reduced and the autoplay clock stopped at the start of its interval. Lagging: ships an earlier revision of this spec, with layout and its three values, and no loop, dots or autoplay, and so owes the hero centred by default, the edge slots of no width, the scaled slots closed up and the constant gap as well (CRL-9 to CRL-13, CRL-26, CRL-40), and the previous and next controls with their props navigation, previousLabel and nextLabel (CRL-41 to CRL-43). Target API: TanreiCarousel(_ label: String, heroAlignment: .center, itemWidth: CGFloat? = nil, fluidWidth: true, contentAlignment: .center, scaleOthers: false, snap: true, loop: false, showsIndicator: false, autoplay: false, autoplayInterval: Duration? = nil, isPaused: Binding<Bool>, showsPlaybackControl: true, showsAutoplayProgress: false, playLabel:, pauseLabel:) { TanreiCarouselItem(…) { … } }; pausedChange is the isPaused binding, set only by the control. A horizontal ScrollView whose content is a track as long as the scroll range, the items placed from the scroll offset by a custom Layout (read from scrollPosition and the track's geometry), settled by a custom ScrollTargetBehavior (iOS 17). The list is an accessibilityElement(children: .contain) named by label, and each item a button. SwiftUI has no position-in-set property for a custom container, so VoiceOver's own count of the container's elements stands in; the README says so. Autoplay reads accessibilityReduceMotion and scenePhase. Snapshot harness: a 400 × 200 frame (400 × 248 with a controls row) on a surface page. Lagging: ships an earlier revision of this spec, with layout and its three values, and no loop, dots or autoplay, and so owes the hero centred by default, the edge slots of no width, the scaled slots closed up and the constant gap as well (CRL-9 to CRL-13, CRL-26, CRL-40), and the previous and next controls with their props navigation, previousLabel and nextLabel (CRL-41 to CRL-43). Target API: TanreiCarousel(itemCount, contentDescription, heroAlignment, itemWidth: Dp? = null, fluidWidth = true, contentAlignment, scaleOthers = false, snap = true, loop = false, indicator = false, autoplay = false, autoplayInterval: Duration? = null, paused = false, onPausedChange, playbackControl = true, autoplayProgress = false, playLabel, pauseLabel) { index -> TanreiCarouselItem(…) { … } }, as Material's carousels take an item lambda; built on Modifier.scrollable and a custom Layout, not Material's carousel (the package does not depend on Material). Settling uses a SnapFlingBehavior whose fling threshold is ViewConfiguration's. No list role: collectionInfo on the row and collectionItemInfo with Role.Button on each item. Autoplay reads the animator duration scale (0 is reduced motion) and the lifecycle's resumed state. Snapshot harness: a 400 × 200 dp box (400 × 248 with a controls row) on a surface page. Lagging: ships an earlier revision of this spec, with layout and its three values, and no loop, dots or autoplay, and so owes the hero centred by default, the edge slots of no width, the scaled slots closed up and the constant gap as well (CRL-9 to CRL-13, CRL-26, CRL-40), and the previous and next controls with their props navigation, previousLabel and nextLabel (CRL-41 to CRL-43). Target API: TanreiCarousel(semanticLabel:, heroAlignment: TanreiCarouselHeroAlignment.center, itemWidth: null, fluidWidth: true, contentAlignment: TanreiCarouselContentAlignment.center, scaleOthers: false, snap: true, loop: false, indicator: false, autoplay: false, autoplayInterval: null, paused: false, onPausedChanged:, playbackControl: true, autoplayProgress: false, playLabel:, pauseLabel:, children: [...]), built on the widgets library's Scrollable with a custom layout and ScrollPhysics that settle (CRL-14), not Material's CarouselView. Each item is Semantics(button: true, label:) inside IndexedSemantics, and the scrollable reports semanticChildCount, so the position and the number of items are read; the whole is a Semantics(label:, explicitChildNodes: true) container. Autoplay reads MediaQuery.disableAnimationsOf and the app lifecycle. Snapshot harness: a golden of a 400 × 200 surface (400 × 248 with a controls row) on a surface page. Lagging: ships an earlier revision of this spec, with layout and its three values, and no loop, dots or autoplay, and so owes the hero centred by default, the edge slots of no width, the scaled slots closed up and the constant gap as well (CRL-9 to CRL-13, CRL-26, CRL-40), and the previous and next controls with their props navigation, previousLabel and nextLabel (CRL-41 to CRL-43). Target API: <Carousel accessibilityLabel heroAlignment itemWidth fluidWidth contentAlignment scaleOthers snap loop indicator autoplay autoplayInterval paused onPausedChange playbackControl autoplayProgress playLabel pauseLabel>{items}</Carousel>: a horizontal ScrollView whose content is as long as the scroll range, the items placed from an Animated value of its offset; it settles with scrollTo after the drag or momentum ends (fling 0.5 pt/ms). The carousel has role="list" and each item the button role. React Native has no position-in-set property, so an item's position and the number of items are left to the screen reader's own count of the list; the README says so. Autoplay reads AccessibilityInfo.isReduceMotionEnabled and AppState. No virtualization. Snapshot harness: a 400 × 200 view (400 × 248 with a controls row) on a surface page.

トークン

トークン 参照先 解決値(ライト)
--tanrei-comp-carousel-autoplay-interval — 5000ms
--tanrei-comp-carousel-container-large-width — 200px
--tanrei-comp-carousel-container-min-scale — 0.85
--tanrei-comp-carousel-container-shape sys.shape.corner.large 12px
--tanrei-comp-carousel-container-small-max-width — 56px
--tanrei-comp-carousel-container-small-min-width — 40px
--tanrei-comp-carousel-controls-button-size — 40px
--tanrei-comp-carousel-controls-height sys.target.touch 48px
--tanrei-comp-carousel-controls-icon-size sys.icon.medium 24px
--tanrei-comp-carousel-controls-indicator-padding-inline sys.space.space150 12px
--tanrei-comp-carousel-controls-shape sys.shape.corner.full 9999px
--tanrei-comp-carousel-dot-active-width — 24px
--tanrei-comp-carousel-dot-color sys.color.outline #848484
--tanrei-comp-carousel-dot-gap sys.space.space100 8px
--tanrei-comp-carousel-dot-size — 8px
--tanrei-comp-carousel-focused-container-shape — 12px
--tanrei-comp-carousel-focused-opacity — 1
--tanrei-comp-carousel-hovered-container-shape — 12px
--tanrei-comp-carousel-hovered-opacity — 1
--tanrei-comp-carousel-indicator-container-color — #000000 · 0%
--tanrei-comp-carousel-indicator-content-color sys.color.primary #1b1b1b
--tanrei-comp-carousel-indicator-focused-container-color — #1b1b1b · 10%
--tanrei-comp-carousel-indicator-focused-content-color — #1b1b1b
--tanrei-comp-carousel-indicator-focused-state-layer-color — #1b1b1b
--tanrei-comp-carousel-indicator-hovered-container-color — #1b1b1b · 8%
--tanrei-comp-carousel-indicator-hovered-content-color — #1b1b1b
--tanrei-comp-carousel-indicator-hovered-state-layer-color — #1b1b1b
--tanrei-comp-carousel-indicator-pressed-container-color — #1b1b1b · 10%
--tanrei-comp-carousel-indicator-pressed-content-color — #1b1b1b
--tanrei-comp-carousel-indicator-pressed-state-layer-color — #1b1b1b
--tanrei-comp-carousel-indicator-state-layer-color comp.carousel.indicator.content.color #1b1b1b
--tanrei-comp-carousel-item-container-color sys.color.surface-container-highest #eeeeee
--tanrei-comp-carousel-item-content-color sys.color.on-surface #1b1b1b
--tanrei-comp-carousel-item-focused-container-color — #d9d9d9
--tanrei-comp-carousel-item-focused-content-color — #1b1b1b
--tanrei-comp-carousel-item-focused-state-layer-color — #1b1b1b
--tanrei-comp-carousel-item-hovered-container-color — #dddddd
--tanrei-comp-carousel-item-hovered-content-color — #1b1b1b
--tanrei-comp-carousel-item-hovered-state-layer-color — #1b1b1b
--tanrei-comp-carousel-item-pressed-container-color — #d9d9d9
--tanrei-comp-carousel-item-pressed-content-color — #1b1b1b
--tanrei-comp-carousel-item-pressed-state-layer-color — #1b1b1b
--tanrei-comp-carousel-item-state-layer-color comp.carousel.item.content.color #1b1b1b
--tanrei-comp-carousel-layout-gap sys.space.space100 8px
--tanrei-comp-carousel-layout-height — 200px
--tanrei-comp-carousel-layout-padding-inline sys.space.space200 16px
--tanrei-comp-carousel-motion-autoplay-duration sys.motion.duration.long2 500ms
--tanrei-comp-carousel-motion-autoplay-easing sys.motion.easing.standard cubic-bezier(0.2, 0, 0, 1)
--tanrei-comp-carousel-motion-feedback-duration sys.motion.role.feedback 450ms
--tanrei-comp-carousel-motion-scroll-duration sys.motion.duration.medium2 300ms
--tanrei-comp-carousel-motion-scroll-easing sys.motion.easing.emphasized-decelerate cubic-bezier(0.05, 0.7, 0.1, 1)
--tanrei-comp-carousel-motion-state-duration sys.motion.role.state.large 250ms
--tanrei-comp-carousel-motion-state-easing sys.motion.easing.state cubic-bezier(0.2, 0, 0, 1)
--tanrei-comp-carousel-navigation-button-variant — 2
--tanrei-comp-carousel-playback-container-color — #000000 · 0%
--tanrei-comp-carousel-playback-content-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-carousel-playback-focused-container-color — #4b4b4b · 10%
--tanrei-comp-carousel-playback-focused-content-color — #4b4b4b
--tanrei-comp-carousel-playback-focused-state-layer-color — #4b4b4b
--tanrei-comp-carousel-playback-hovered-container-color — #4b4b4b · 8%
--tanrei-comp-carousel-playback-hovered-content-color — #4b4b4b
--tanrei-comp-carousel-playback-hovered-state-layer-color — #4b4b4b
--tanrei-comp-carousel-playback-pressed-container-color — #4b4b4b · 10%
--tanrei-comp-carousel-playback-pressed-content-color — #4b4b4b
--tanrei-comp-carousel-playback-pressed-state-layer-color — #4b4b4b
--tanrei-comp-carousel-playback-state-layer-color comp.carousel.playback.content.color #4b4b4b
--tanrei-comp-carousel-pressed-container-shape — 12px
--tanrei-comp-carousel-pressed-opacity — 1

フィクスチャ

基準となる描画パターンです。全プラットフォームが同じ ID でスナップショットを撮り、結果を比較できるようにしています。

生成元 specs/carousel.spec.json, tokens/comp/carousel.tokens.json, design/tokens/

ガイドライン

使う場面

人が眺めて 1 つずつ開く、ビジュアルの項目の列に使います。アルバム、場所、商品、エピソードなどです。グリッドほどの高さを取らずに、まだ続きがあることを示せます。また、見えている項目には、これから現れる項目より広い場所を与えます。

項目がほぼテキストのときや、人が項目を見比べて選ぶときはリストを使います。項目ごとにテキストやアクションが必要なら、カードをグリッドに並べます。カルーセルを自動で進めるのは短い目玉の列だけにして、再生コントロールを残してください。

列の並べ方

プロパティはそれぞれ 1 つの問いに答え、自由に組み合わせられます。

プロパティ 既定値 決めること
heroAlignment center フォーカスのある項目の位置。両側に項目が並ぶ中央(center)か、あとにだけ項目が続く内側の先頭側の端(start)か。
itemWidth デザインシステムの値 ヒーローの幅。幅が固定のときは、すべての項目の幅。
fluidWidth true true ならヒーローの隣の項目を中くらいと小さい幅へ狭め、それぞれの幅がスクロールに従います。false ならすべての項目がヒーローと同じ幅になり、見えている最後の項目が端で切れます。
contentAlignment center 幅が流動的なとき、狭まった項目のコンテンツのどこを見せるか。中央か、ヒーローに近い側(toward-hero)か。
scaleOthers false ヒーロー以外の項目を、スクロールに従って小さく描きます。幅が流動的でも固定でも使えます。項目の間隔は変わりません。幅が固定のときは、小さくなった項目が隣に詰まります。
snap true スクロールが終わったとき、項目の切れ目で止まるか、終わった場所で止まるか。
loop false 列を終わりのないものにします。最後の項目の次に最初の項目が来ます。項目が 2 つ以上のときだけ働きます。

Web では、既定が true のプロパティの逆として、fluidWidth は fixed-width、snap は free-scroll と書きます。

よくある列:

用途 設定
アルバムや場所など、多くの項目を眺める 何もしない(既定値)。
目玉の項目を 1 つずつ見せ、次をちらりとのぞかせる カルーセル以上の幅の itemWidth。ヒーローが取れる最大の幅になります。
両側に項目が並ぶ目玉の列 追加の設定は不要です(ヒーローは既定で中央)。多くは loop と組み合わせ、両側を常に埋めます。
端から始まり、ヒーローが先頭に来る列 heroAlignment を start に。
最近見た項目のような、素朴なスクロールする列 heroAlignment を start に、fluidWidth と snap を false に。

itemWidth はコンテンツの形に合わせて選びます。正方形のジャケット、縦長のポスター、横長のサムネイルでは、同じプロダクトの中でも合う幅が違います。幅が流動的なとき、ヒーローはほかの項目のための場所を残します。項目が続く側それぞれに小さい項目 1 つ分を残す幅より広くはならず、次の項目が自分より広くならない幅より狭くもなりません。それだけの幅もない列では、列いっぱいの幅の項目を 1 つずつ見せます。

ヒーローは既定で中央

以前の版とは 2 つの点が違います。ほかのプラットフォームが追従するまでは、Web の動作です。

  • heroAlignment の既定値が center になりました。 以前は start でした。どちらの値も引き続き使えます。
  • 隣り合う項目の間隔は、止まっているときだけでなく、スクロールのどの位置でも一定です。 以前は、項目が列の端を通るときに間隔が広がり、縮小した項目のまわりに余分な空きができていました。
これまで これから
heroAlignment を指定していない ヒーローが中央に来て、両側に狭い項目が並びます。以前のままにするには、heroAlignment を start にします(Web では hero-alignment="start")。
heroAlignment が start 変更は不要です。
heroAlignment が center 変更は不要です。プロパティは外してもかまいません。
幅が固定で scaleOthers を使っている 小さくなった項目が、隣から間隔 1 つ分のところに並びます。以前より詰まります。変更は不要です。
列より広い itemWidth を指定し、heroAlignment は指定していない 両側に小さい項目が 1 つずつ残るため、ヒーローは以前より狭くなります。最大の幅にするには heroAlignment を start にします。

幅が流動的なとき、項目は全幅のまま滑り込むのではなく、端で幅のないところから現れ、入ってくるにつれて広がります。止まっているときの見た目は変わりません。

layout からの移行

以前の版の layout プロパティはなくなりました。それぞれの値は、上のプロパティの組み合わせになります。

以前の layout これから
既定値のまま heroAlignment を start に。スマートフォンほどの幅までの列では同じ見た目です。それより広い列では、ヒーローが上限まで広がります。
hero heroAlignment を start に、itemWidth をカルーセル以上の幅に。ヒーローが取れる最大の幅になり、同じ見た目です。
uncontained heroAlignment を start に、fluidWidth と snap を false に(Web では hero-alignment="start" fixed-width free-scroll)。同じ見た目です。

ドット、自動再生、再生コントロール

  • ドット。 indicator を指定すると、止まる位置ごとに 1 つのドットを、項目の下の行にカルーセルの中央へそろえて描きます。現在のドットは横に伸び、列がスクロールするにつれて、その伸びが隣のドットへ移ります。ドット全体で 1 つのコントロールで、どこを押してもドラッグしても列が動きます。短い目玉の列に使います。再生コントロールの横に収まらないほど多ければ描かず、デバッグビルドで報告します。
  • 自動再生。 autoplay を指定すると、autoplayInterval ミリ秒(既定のシステムでは 5 秒)ごとに列が 1 項目ずつ進みます。テキストの多い項目には、画像より長い時間を与えてください。loop がなければ、最後の項目の次は最初の項目へ戻ります。フォーカスを動かすことも、何かを読み上げることもありません。
  • 保留。 自動再生は、ポインターがカルーセルの上にある間、フォーカスがカルーセルのどこかにある間、押されているかドラッグされている間、ウィンドウが隠れている間は止まります。最後の保留が終わると、止まったところから続けます。人が列を動かしたあとは、間隔を最初から数え直します。
  • 動きを減らす設定。 プラットフォームの動きを減らす設定がオンのときは、自動再生は動かず、再生コントロールと進み具合も描きません。幅、縮小、ドットは、人が自分で行うスクロールには従います。
  • 再生コントロール。 自動再生の間、ドットの行の終わりにある丸いボタンで一時停止と再生を切り替えます。グリフは pause か play_arrow です。これから行う操作に応じて pauseLabel か playLabel を名前とします。paused を変え、pausedChange(Web では pausedchange)を発火します。コードから paused を設定することもでき、そのときは何も発火しません。
  • 進み具合。 autoplayProgress を指定すると、次の項目までの時間に合わせて現在のドットが満ちていきます。

再生を止められるようにしておいてください。 playbackControl を false にすれば再生コントロールを隠せますが、できる限り残すべきです。保留は動きを止める手段になりません。タッチにはホバーがなく、指は置いている間しか列を止められず、フォーカスを動かさずに読み進めるスクリーンリーダーはまったく止めません。コントロールがなければ、スマートフォンの利用者が自動再生を止める方法は、システムの動きを減らす設定しかありません。隠すのは、ページ上の動くコンテンツをすべて止める機能など、動きを止める別の方法をプロダクトが用意している場合だけにしてください(WCAG 2.2.2)。

前と次のコントロール

スクロールバーのない列では、マウスで列を動かす 1 回の押下がありません。あるのは Shift を押しながらのホイールか、描かれていればドットだけです。そのためカルーセルは、列の両端の上に「前へ」と「次へ」のコントロールを描きます。山形のグリフを持つアイコンだけの Button で、バリアントはデザインシステムが決めます(既定のシステムでは elevated)。それぞれ、ドットや矢印キーと同じく、列を 1 項目戻すか進めます。

navigation 描かれる場所 見える間
auto(既定) 主なポインタがホバーできるマウスやトラックパッドのとき ポインタがカルーセルの上にあるか、フォーカスがその中にある間
always タッチを含むすべての端末 常に
none なし なし
  • 既定でタッチに描かない理由。 指は列をスワイプでき、スマートフォンではコントロールがずっと項目の上に重なってしまうためです。タッチでも必要な利用者がいるなら always を選んでください。
  • 両端で。 loop がなければ、最初の項目では「前へ」が、最後の項目では「次へ」が無効になります。無効になったコントロールもフォーカスと Tab の順番の位置を保つので、「次へ」を押し続けてもフォーカスが失われることはありません。loop のときは一周します。
  • 続けて押すと加算されます。 列が動いている間に押すと、向かっている位置から数えるので、素早く 2 回押せば 2 項目進みます。
  • 自動再生。 押すことは利用者自身の操作です。列が止まってから、間隔が最初から数え直されます。ポインタがコントロールの上にあるか、フォーカスがその上にある間は、カルーセルのほかの場所と同じく自動再生が止まります。
  • 名前。 previousLabel と nextLabel が支援技術向けの名前です(たとえば「前の散歩道」「次の散歩道」)。ないときはデバッグビルドで報告します。

できる限り auto のままにしてください。none を選ぶのは、ドットなど、マウスで列を動かす別の手段をページが用意している場合だけにします。

項目とそのコンテンツ

  • コンテンツはヒーローの幅でレイアウトする。 どの項目も、コンテンツをヒーローの幅でレイアウトします。そのため狭まった項目には画像の中央部分(contentAlignment が toward-hero ならヒーローに近い側)が見え、項目が伸び縮みするにつれて画像が中で滑るように動きます。見える部分だけでも意味が伝わる画像を使ってください。
  • 項目にテキストを載せない。 画像の上のテキストは、どのトークンでもコントラストを保証できないので、項目はキャプションを描きません。キャプションが必要なら、利用者側のスクリムを敷いてコンテンツの中に描くか、カルーセルの外に置きます。
  • 1 つの項目に 1 つのアクション。 項目は、それが示すものを開きます。ボタン、リンク、そのほかのコントロールをコンテンツに入れないでください。
  • 無効な項目はない。 眺めるための列に、見えているのに使えない項目は要りません。そうした項目は列から外します。
  • 空の項目も見える。 項目のコンテナは、項目の状態にかかわらず、コンテンツの下を通常時の色で塗るので、画像がまだ読み込まれていない項目も見分けられます。
  • 状態はコンテンツの上に表れる。 ホバー、フォーカス、押下は、コンテンツの上に重ねた層として描き、リップルも同じ層に描きます。そのため、どんな画像の上でも見えます。

スクロールと止まる位置

列は、タッチ、ペン、トラックパッド、横方向のホイール、Shift を押しながらの縦方向のホイールでスクロールできます。snap のときは、スクロールが終わると列が項目の切れ目に止まります。フリングならその向きの次の項目へ、ゆっくり離したなら近いほうの項目へ進みます。フリングとは、プラットフォームの最小フリング速度(定めがなければ 1 ミリ秒あたり 0.5 ポイント)より速く離すことです。loop がなければ、範囲の終わりでは最後の項目がヒーローになり、その後ろの枠は空のままです。loop のときは、各項目をヒーローのまわりの当てはまる位置に 1 回だけ描き、列は終わりなく回ります。動きを減らす設定では、カルーセルが自分で始める動きはすべて瞬時に終わります。マウスでは、「前へ」と「次へ」のコントロールで 1 項目ずつ動かせます。

カルーセルの幅や項目が変わると、枠を並べ直し、それまでフォーカスのあった項目を、動きなしで同じ場所に保ちます。

推奨

  • 各項目に、コンテンツが何を示すかをプロダクトの言語でラベルとして付けます。
  • itemWidth はコンテンツの形に合わせて選びます。
  • 「最近再生した曲」のように、列を名付ける label をカルーセルに付けます。
  • 自動で進むカルーセルには必ず再生コントロールを残し、playLabel と pauseLabel を付けます。

非推奨

  • 項目のコンテンツの中にコントロールを置かない。項目自体がすでにボタンです。
  • 手順や規約のような、すべてを見てもらう必要があるコンテンツにカルーセルを使わない。見えていない項目は見落とされがちです。
  • 眺めるための長い列で自動再生を使わない。自動再生は、ドットのある短い目玉の列に向いています。
  • 動きを止める別の方法をプロダクトが用意していないのに、再生コントロールを隠さない。

アクセシビリティ

ロール
list
ラベル付け
The items are a list named by label. Each item is one button named by its label, with its position and the number of items; its content is not read. The indicator is one slider named by label, whose value text is the current item's label. The playback control is a button named by pauseLabel while playing and by playLabel while paused. Items out of view are still reachable, and focusing one scrolls it into place. The previous and next controls are buttons named by previousLabel and nextLabel, disabled at the ends of a row that does not loop.
最小ターゲット
見た目のサイズが小さくても、ヒット領域は最小ターゲットサイズを満たします。 minimum 32px · pointer 44px · spacing 8px · touch 48px

キーボード操作

キー 動作
Tab / Shift+Tab Moves through the carousel's stops: the playback control (while it is drawn), the previous and next controls (while they are drawn), then the items as one stop (the item that last had focus, or else the resting item), then the indicator (while it is drawn), and on out (CRL-17).
Right Arrow / Left Arrow On an item: moves focus to the next or previous item and brings it into place. On the indicator: moves the row to the next or previous position. Both wrap only while the carousel loops, and are mirrored in right-to-left layouts.
Home / End On an item: moves focus to the first or last item and brings it into place. On the indicator: moves the row to the first or last position.
Enter / Space On an item: activates it (its click). On the playback control: pauses or plays autoplay. On the previous or the next control: moves the row back or on by one position (CRL-43).

備考

  • Scrolling is never the only way to reach an item (WCAG 2.5.7, 2.1.1): the previous and next controls move the row one position with a single press of a mouse — drawn by default wherever the primary pointer is fine and can hover —, and the arrow keys, the indicator and screen-reader focus move through every item and bring it into place. The row draws no scroll bar, so without the controls a mouse could move the row only with Shift and the wheel.
  • Where the previous and next controls are, and when. They stand over the ends of the row, as the arrows of Ant Design's and Mantine's carousels and the overlay layouts of Fluent 2's do, not in the controls row, which exists only for a featured row with dots or autoplay. By default (auto) they are drawn only for a pointer that is fine and can hover, and shown while it is over the carousel or focus is within it: a touch screen moves the row with the finger's own swipe, and there the controls would stand over the items for good. A product whose people need them on touch as well sets always. Their place in the Tab sequence is the WAI-ARIA carousel example's: the playback control, then previous and next, then the slides.
  • A previous or next control at the end of a row that does not loop is disabled but keeps focus and its place in the Tab order (the Button's focusableWhenDisabled), so that pressing next again and again never drops focus to the page, and the number of stops does not change with the scroll.
  • An item entering or leaving at an edge of a row with fluid widths is, between resting positions, narrower than a touch target (CRL-4, CRL-12). A row that snaps never rests there; a row that does not snap can, and the arrow keys, the indicator and a further scroll all bring that item to a slot, where it has its whole target.
  • Autoplay (WCAG 2.2.2 Pause, Stop, Hide). A carousel that advances by itself holds while it is hovered, while focus is anywhere in it, while it is pressed and while it is hidden, and it never advances where the platform's reduced-motion setting is on (CRL-33, CRL-34). While autoplay is on, the playback control is drawn by default and is the first Tab stop, so a keyboard user reaches it before the items (CRL-17).
  • Keep playback controllable. A product may hide the playback control (playbackControl false), but it should keep it wherever it can. The holds are not a way to stop the motion: on touch there is no hover, a finger on the carousel holds it only while it stays there, and a screen reader that reads without moving focus does not hold it at all. Without the control, a person on a phone can stop an autoplaying carousel only through the system's reduced-motion setting. Hide it only where the product gives the person another way to stop the motion, such as a pause for all moving content on the page.
  • Autoplay makes no announcement and never moves focus (CRL-32). A person who wants to know what changed finds the current item as the indicator's value.
  • Loop keeps the arrow keys and the indicator going round, but Home and End still go to the first and last item, so a person can always find the ends of an endless row.
  • The indicator is one slider, not a row of unlabelled buttons: one Tab stop, the arrow keys to move, the current item's label as its value. Its hit area is the whole container and the controls row's height, so the dots need no target of their own.
  • The current dot is told apart by its stretched shape, not by color alone (WCAG 1.4.1), and the other dots use the functional outline role (3:1 against the surface).
  • Web: the host has no role; a shadow element with role list, named by label, holds the item slot, so the slotted `<tanrei-carousel-item>` elements (role listitem, around a shadow element with role button and tabindex, roving: 0 on the Tab stop, -1 on the others) are its items, as the interactive card draws its button. The indicator is a shadow element with role slider and tabindex 0, and the playback control a native `<button>`; both sit after the list, outside it. Compose has no list role: the row gives `collectionInfo` and each item `collectionItemInfo` with `Role.Button`; the indicator is a `progressBarRangeInfo` with `setProgress`. Flutter reads each item's position through `IndexedSemantics`, and the indicator is a `Semantics(slider: true)` with increase and decrease actions. SwiftUI's indicator is `accessibilityAdjustableAction`, React Native's the adjustable role with increment and decrement actions. SwiftUI and React Native have no position-in-set property for a custom container, so there the screen reader's own count of the items stands in for CRL-19's position and number; their READMEs say so.
  • How a state looks is a value decision of the active design system; components read the state's tokens and never compute the effect themselves.

生成元 specs/carousel.spec.json, tokens/sys/measure.tokens.json

コンポーネントが担うこと

  • カルーセルは、label を名前とするリストです。各項目は、ラベルを名前とし、項目の中での位置と項目の数を持つ 1 つのボタンです。コンテンツは支援技術から隠します。
  • 見えていない項目も順番どおりに伝えるので、スクリーンリーダーはすべての項目に届きます。スクリーンリーダーのフォーカスを項目に移すと、その項目が所定の位置までスクロールします。
  • Tab で止まる位置。 最大で 5 か所です。再生コントロール(描かれているとき)、前と次のコントロール(描かれているとき)、1 か所としての項目、ドット(描かれているとき)です。再生コントロールは項目より先に来るので、キーボードの利用者はほかの何よりも先に動きを止められます。前と次のコントロールは、WAI-ARIA のカルーセルの例と同じく項目より前に来ます。項目には、最後にキーボードフォーカスのあった項目、なければヒーローの位置に止まっている項目から入ります。
  • 右矢印と左矢印で次と前の項目へ移ります。端では止まり、ループしているときだけ折り返します。Home と End は、ループしている列でも最初と最後の項目へ移ります。右から左のレイアウトでは、左矢印で次の項目へ移ります。
  • フォーカスを動かすと、その項目をヒーローの位置まで(範囲の終わりが先に来るときはできるだけ近くへ)スクロールします。ループしている列では近いほうを回ります。幅が固定で自由にスクロールする、先頭にヒーローのある列では、全体が見えるところまでだけスクロールします。
  • Enter と Space でフォーカス中の項目を実行します。スクロールに変わった押下では、押し始めた項目は実行されません。
  • ドットは label を名前とする 1 つのスライダーで、値は現在の項目のラベルです。矢印キー、Home と End、プラットフォームの増減の操作で、列が 1 位置ずつ動きます。現在のドットは、色だけでなく伸びた形で見分けられます。
  • 再生コントロールは、再生中は pauseLabel、一時停止中は playLabel を名前とするボタンです。押された状態を持たないので、「一時停止、押されています」のように読み上げられることはありません。実行してもフォーカスはボタンに残ります。一時停止と再生のグリフは左右反転せず、アイコンフォントの読み込み前にグリフの名前がテキストとして描かれることもありません。
  • 前と次のコントロールは、previousLabel と nextLabel を名前とするボタンです。押してもフォーカスはそのまま残ります。ループしない列の両端では無効になりますが、フォーカスを保ち、Tab の順番からも外れません。auto で隠れている間も Tab で届き、スクリーンリーダーに読まれ、フォーカスが来ると表示されます。右から左のレイアウトでは「前へ」が右側に来て、山形のグリフはアイコンセットの指定どおりに反転します。
  • 自動再生は、フォーカスがカルーセルのどこかにある間は止まり、フォーカスを動かさず、何も読み上げず、動きを減らす設定では動きません。
  • フォーカスリングは、列に切り取られないよう項目の内側に描き、ドットと再生コントロールではそのまわりに描きます。キーボードフォーカスのときだけ表示します。
  • 各項目のヒット領域はコンテナに両側の間隔の半分ずつを加えた範囲で、小さい項目でも最小ターゲットを下回りません。端で出入りしている途中の項目はそれより狭く、そのヒット領域が隣の項目のヒット領域に重なることはありません。再生コントロールのヒット領域はタッチターゲットの大きさ、ドットのヒット領域はコンテナ全体と行の高さです。
  • スクロールだけが項目に届く唯一の手段になることはありません。前と次のコントロールで、マウスの 1 回の押下で列を動かせ(WCAG 2.5.7)、矢印キー、ドット、スクリーンリーダーのフォーカスで、すべての項目に届きます。
  • 文字を大きくしても、カルーセルの高さと項目は変わりません。カルーセル自体はテキストを持たず、コンテンツが自分で文字の大きさに従います。
  • ラベルのない項目、label のないカルーセル、playLabel か pauseLabel のない再生コントロール、previousLabel か nextLabel のない前と次のコントロール、収まらないドット、heroAlignment、contentAlignment、navigation の知らない値、正の数でない itemWidth と autoplayInterval は、デバッグビルドで報告します。Web では、項目のコンテンツの中にある操作できる要素も報告します。

プラットフォームごとの既知の制約:

  • SwiftUI と React Native には、独自のコンテナの中での位置を伝えるプロパティがありません。そのため項目の位置と数は、スクリーンリーダー自身が数えたもので代わりにします。SwiftUI にはリストのロールもなく、カルーセルは label を名前とするコンテナです。
  • Compose にはリストのロールがありません。列が項目の数を、各項目がボタンのロールとともに位置を伝えるので、TalkBack にはリストではなくコレクションとして読み上げられます。
  • 項目のコンテンツの中を調べられるのは Web だけなので、中のコントロールを報告するのも Web だけです。ほかのプラットフォームでは、この決まりは利用者側で守ってください。
  • Web:仕様では Tab で止まる項目は最後にキーボードのフォーカスを持った項目ですが、Web ではクリックも含め、フォーカスを受け取ったどの項目にも移ります。ペンでフリックすると、次の項目ではなく最も近い項目で止まります。フォーカスを動かさないスクリーンリーダーの仮想カーソルでは、ブラウザーのとおりに列がスクロールするだけで、その項目はヒーローの位置まで来ません。
  • SwiftUI:カルーセルの高さは常に高さのトークンのとおりです。SwiftUI ではフレームの指定とスタックからの提案を区別できないためです。高さはレイヤのトークンで変えてください。止まるときの動きはスクロールビュー自身の減速で、カルーセルのスクロールの動きのトークンに従わず、動きを減らす設定でもすぐには止まりません。すべての項目を作り、スクロールのフレームごとにすべてを置き直します。矢印キー、Tab で止まる項目、VoiceOver のフォーカスは規則としてテストしていますが、実機のキーボードや VoiceOver では試していません。
  • Compose は、見えていない項目も含め、すべての項目を組み立てて配置します。フリングにならないホイールでは、最も近い項目で止まります。スクロールは Robolectric でだけ確かめており、実機では試していません。
  • Flutter は、切れ目で止まるカルーセルを、ホイールの操作が終わったときではなく、マウスホイールのイベントのたびに止めます。実機の TalkBack と VoiceOver では、まだ確かめていません。
  • React Native は、スクロールのフレームごとに、JavaScript のスレッドですべての項目を配置し直し、すべての項目を描きます。矢印キーは、ハードウェアキーボードのイベントがアプリに届く環境でしか使えません。スクロールに合わせた配置、止まる動き、右から左、スクリーンリーダーのフォーカスは Jest でだけ確かめており、実機では試していません。
  • m3-like では、Material のカルーセルがサーフェスの色を使うところを、意図して既定の塗りのままにしています。画像がまだ読み込まれていない項目も見分けられるようにするためです。

利用者側で行うこと

  • カルーセルに label を、すべての項目にコンテンツが何を示すかを表すラベルを付けます。コンテンツは読み上げられません。
  • 自動再生のときは、再生コントロールに何をするかが分かる playLabel と pauseLabel を付け、コントロールを描いたままにします。
  • 前と次のコントロールに previousLabel と nextLabel を付けます。ページがマウスで列を動かす別の手段を用意していない限り、navigation は auto のままにします。
  • 項目のコンテンツにコントロールを置かないでください。
  • ポスターに書かれた価格のように、ラベルにない意味をコンテンツが持っているなら、それもラベルに含めます。
  • SwiftUI、Compose、React Native では、項目の数を控えめにします。これらのカルーセルは、見えていない項目も含めてすべてを作ります。