as

Settings
Sign out
Notifications
Alexa
Amazon Appstore
AWS
Documentation
Support
Contact Us
My Cases
Get Started
Design and Develop
Publish
Reference
Support

Carousel

The Carousel component implements a single row of content metadata tiles in a Vega app's UI screen. Carousels are typically used in home screen grids, in More like this or Related content rows, among other use cases.

    Version 3.0.0 updates

    The following improvements have been made from the previous version of Carousel:

    • Added a contract to the Carousel between the JavaScript and native sides to give the Carousel complete control over how many item frames it contains and the lifecycle of those items. This fix issues with recycling that were present in Carousel 2.0.0.
    • Removed layout props like getItemLayout and layoutId because the Carousel now determines the layout automatically.
    • Expanded the list of props to allow for greater customization of the Carousel.
    • Added a new callback prop to notify you whenever the current selected item on the Carousel changes.
    • The library is now app bundled rather than system bundled (see the following section for more details).

    App bundled

    This version of Carousel is app bundled, which requires an npm update before releases to make sure you have the latest version. App bundling gives you more control of when you bring in new Carousel changes to your app.

    App bundling increases the size of your app VPKG because the library is now bundled with the app. Expect an increase the size of your app bundle by approximately 300K for release VPKGs and 500K for debug VPKGs.

    Usage

    This version of Carousel is included in @amzn/kepler-ui-components ^3.0.0. Check your package.json to confirm the version is set as shown below.

    Copied to clipboard.

    "dependencies": {
      // ...
      "@amzn/kepler-ui-components": "^3.0.0"
    }
    

    Import

    Copied to clipboard.

    import { Carousel } from '@amzn/kepler-ui-components';
    

    Examples

    Horizontal Carousel

    Copied to clipboard.

    
    import React, {memo} from 'react';
    import {Image, Pressable, View} from 'react-native';
    import {
      Carousel,
      CarouselRenderInfo,
    } from '@amzn/kepler-ui-components';
    import {useState} from 'react';
    
    import {ItemType, ScrollableProps} from './Types';
    import {CAROUSEL_STYLE} from './Style';
    
    export const HorizontalScrollable = ({data}: ScrollableProps) => {
      function ItemView({item}: CarouselRenderInfo<ItemType>) {
        const [focus, setFocus] = useState<boolean>(false);
        const onFocusHandler = () => {
          setFocus(true);
        };
        const onBlurHandler = () => {
          setFocus(false);
        };
    
        return (
          <Pressable
            style={[
              CAROUSEL_STYLE.itemContainer,
              focus && CAROUSEL_STYLE.itemFocusContainer,
            ]}
            onFocus={onFocusHandler}
            onBlur={onBlurHandler}>
            <Image style={CAROUSEL_STYLE.imageContainer} source={{uri: item.url}} />
          </Pressable>
        );
      }
    
      const renderItemHandler = ({item, index}: CarouselRenderInfo<ItemType>) => {
        return <ItemView index={index} item={item} />;
      };
    
      const getItemKeyHandler **=** (info: CarouselRenderInfo) **=>**
        `${info.index}-${info.item.url}`;
        
      const getItem = useCallback((index: number) => {
        console.info('getItem called for index:', index);
        if (index >= 0 && index < data.length) {
          return data[index];
        }
        return undefined;
      }, [data]);
      
      const getItemCount = useCallback(() => {
        const count = data.length;
        console.info('getItemCount called, returning:', count);
        return count;
      }, [data]);
     
      const notifyDataError = (error: any) => {
        return false; // Don't retry
      };
    
      return (
        <View style={CAROUSEL_STYLE.container}>
          <Carousel
            orientation={'horizontal'}
            containerStyle={CAROUSEL_STYLE.horizontalCarouselContainerStyle}
            itemPadding={20}
            renderItem={renderItemHandler}
            getItemKey={getItemKeyHandler}
            getItem={getItem}
            getItemCount={getItemCount}
            notifyDataError={notifyDataError}
            hasPreferredFocus
            hideItemsBeforeSelection={false}
            numOffsetItems={2}
            selectionStrategy={'anchored'}
            renderedItemsCount={10}
            navigableScrollAreaMargin={100}
            initialStartIndex={0}
            trapSelectionOnOrientation={false}
            selectionBorder={{
              borderColor: '#FFFFFF',
              borderWidth: 2,
              borderRadius: 0,
              borderStrokeRadius: 0
            }}
          />
        </View>
      );
    };
    
    export const HorizontalMemoScrollable = memo(HorizontalScrollable);
    
    

    Vertical Carousel

    Copied to clipboard.

    
    import {Image, Pressable, View} from 'react-native';
    import {
      Carousel,
      CarouselRenderInfo,
    } from '@amzn/kepler-ui-components';
    import {memo, useState} from 'react';
    import React from 'react';
    import {ItemType, ScrollableProps} from './Types';
    import {CAROUSEL_STYLE} from './Style';
    
    export const VerticalScrollable = ({data}: ScrollableProps) => {
      function ItemView({item}: CarouselRenderInfo<ItemType>) {
        const [focus, setFocus] = useState<boolean>(false);
        const onFocusHandler = () => {
          setFocus(true);
        };
        const onBlurHandler = () => {
          setFocus(false);
        };
    
        return (
          <Pressable
            style={[
              CAROUSEL_STYLE.itemContainer,
              focus && CAROUSEL_STYLE.itemFocusContainer,
            ]}
            onFocus={onFocusHandler}
            onBlur={onBlurHandler}>
            <Image style={CAROUSEL_STYLE.imageContainer} source={{uri: item.url}} />
          </Pressable>
        );
      }
    
      const renderItemHandler = ({item, index}: CarouselRenderInfo<ItemType>) => {
        return <ItemView index={index} item={item} />;
      };
    
      const getItemKeyHandler = (info: CarouselRenderInfo) =>
        `${info.index}-${info.item.url}`;
        
      const getItem = useCallback((index: number) => {
        console.info('getItem called for index:', index);
        if (index >= 0 && index < data.length) {
          return data[index];
        }
        return undefined;
      }, [data]);
      
      const getItemCount = useCallback(() => {
        const count = data.length;
        console.info('getItemCount called, returning:', count);
        return count;
      }, [data]);
     
      const notifyDataError = (error: any) => {
        return false; // Don't retry
      };
    
      return (
        <View style={[CAROUSEL_STYLE.container]}>
          <Carousel
            containerStyle={CAROUSEL_STYLE.verticalCarouselContainerStyle}
            orientation={'vertical'}
            itemPadding={10}
            renderItem={renderItemHandler}
            getItemKey={getItemKeyHandler}
            getItem={getItem}
            getItemCount={getItemCount}
            notifyDataError={notifyDataError}
            hasPreferredFocus
            hideItemsBeforeSelection={false}
            selectionStrategy={'anchored'}
            numOffsetItems={2}
            renderedItemsCount={11}
            itemScrollDelay={0.2}
          />
        </View>
      );
    };
    
    export const VerticalMemoScrollable = memo(VerticalScrollable);
    
    

    Heterogeneous Carousel

    Copied to clipboard.

    
    import {Image, Pressable} from 'react-native';
    import {
      Carousel,
      CarouselRenderInfo,
    } from '@amzn/kepler-ui-components';
    import {useCallback, useState} from 'react';
    import React from 'react';
    import {ItemType, ScrollableProps} from './Types';
    import {CAROUSEL_STYLE} from './Style';
    
    export const HeterogeneousItemViewScrollable = ({data}: ScrollableProps) => {
    
      function ItemViewType1({item}: CarouselRenderInfo<ItemType>) {
        const [focus, setFocus] = useState<boolean>(false);
    
        const onFocusHandler = useCallback(() => setFocus(true), []);
        const onBlurHandler = useCallback(() => setFocus(false), []);
    
        return (
          <Pressable
            style={[
              CAROUSEL_STYLE.itemContainerType1,
              focus && CAROUSEL_STYLE.itemFocusContainer,
            ]}
            onFocus={onFocusHandler}
            onBlur={onBlurHandler}>
            <Image style={CAROUSEL_STYLE.imageContainer} source={{uri: item.url}} />
          </Pressable>
        );
      }
    
      function ItemViewType2({item}: CarouselRenderInfo<ItemType>) {
        const [focus, setFocus] = useState<boolean>(false);
    
        const onFocusHandler = useCallback(() => setFocus(true), []);
        const onBlurHandler = useCallback(() => setFocus(false), []);
        return (
          <Pressable
            style={[
              CAROUSEL_STYLE.itemContainerType2,
              focus && CAROUSEL_STYLE.itemFocusContainer,
            ]}
            onFocus={onFocusHandler}
            onBlur={onBlurHandler}>
            <Image
              style={CAROUSEL_STYLE.imageContainer}
              resizeMode="cover"
              source={{uri: item.url}}
            />
          </Pressable>
        );
      }
    
      const renderItemHandler = ({item, index}: CarouselRenderInfo<ItemType>) => {
        return index % 2 === 0 ? (
          <ItemViewType1 index={index} item={item} />
        ) : (
          <ItemViewType2 index={index} item={item} />
        );
      };
    
      const getItemKeyHandler = (info: CarouselRenderInfo) =>
        `${info.index}-${info.item.url}`;
        
      const getItem = useCallback((index: number) => {
        console.info('getItem called for index:', index);
        if (index >= 0 && index < data.length) {
          return data[index];
        }
        return undefined;
      }, [data]);
      
      const getItemCount = useCallback(() => {
        const count = data.length;
        console.info('getItemCount called, returning:', count);
        return count;
      }, [data]);
     
      const notifyDataError = (error: any) => {
        return false; // Don't retry
      };
    
      return (
        <Carousel
          containerStyle={CAROUSEL_STYLE.horizontalCarouselContainerStyle}
          orientation={'horizontal'}
          itemPadding={40}
          renderItem={renderItemHandler}
          getItemKey={getItemKeyHandler}
          getItem={getItem}
          getItemCount={getItemCount}
          notifyDataError={notifyDataError}
          hasPreferredFocus
          hideItemsBeforeSelection={false}
          selectionStrategy={'anchored'}
          numOffsetItems={3}
          renderedItemsCount={11}
          navigableScrollAreaMargin={100}
        />
      );
    };
    
    

    Style.ts

    Copied to clipboard.

    
    import {StyleSheet} from 'react-native';
    
    export const CAROUSEL_STYLE = StyleSheet.create({
      container: {
        backgroundColor: '#000000',
        width: '100%',
        height: '100%',
        justifyContent: 'center',
        alignItems: 'center',
      },
      imageContainer: {
        flex: 1,
        margin: '3%',
      },
      itemContainer: {
        flex: 1,
      },
      itemFocusContainer: {
        borderWidth: 2,
        borderColor: '#FFFFFF',
        borderRadius: 4,
      },
      itemContainerType1: {
        height: 350,
        width: 200,
        justifyContent: 'center',
        alignContent: 'center',
      },
      itemContainerType2: {
        height: 350,
        width: 500,
        justifyContent: 'center',
        alignContent: 'center',
      },
      horizontalCarouselContainerStyle: {
        width: '100%',
      },
      verticalCarouselContainerStyle: {
        height: '100%',
        justifyContent: 'center',
      },
    });
    
    

    Types.ts

    Copied to clipboard.

    
    export type ItemType = {
      url: string;
    };
    
    export type ScrollableProps = {
      data: ItemType[];
    };
    
    

    Features

    Data adapter

    Carousel 3.0.0 supports more customization by allowing you to provide a dataAdapter containing various callback functions. You can see the details for the functions in the Props section below.

    Assuming you have an array called data containing the Carousel item information, you can implement the dataAdapter functions as shown in the following example.

    
    const getItem = useCallback((index: number) => {
        if (index >= 0 && index < data.length) {
            return data[index];
        }
        return undefined;
    }, [data]);
    
    const getItemCount = useCallback(() => {
        return data.length;
    }, [data]);
    
    const getItemKey = (info: CarouselRenderInfo) =>
       `${info.url} ${info.item.index}`
    
    const notifyDataError = (error: any) => {
        return false; // Don't retry
    };
    
    <Carousel
       dataAdapter={{
            getItem,
            getItemCount,
            getItemKey,
            notifyDataError
       }}
       ...
     />
    
    

    Selection Strategy

    The SelectionStrategy prop controls how a list scrolls and repositions its items when you navigate through them using the D-Pad.

    Anchored

    The anchored style causes the selected item to stay locked on the initial item’s position. When scrolling, the items reposition so that the selected item remains anchored to that position.

    Natural

    The natural style causes the selected item to move in the direction of the Carousel's orientation until it reaches the start or end of the list.

    Pinned

    The pinned style pins the selected item at a specified position during scrolling for larger sets of items. As your user approaches the beginning or end of the list, the scroll behavior transitions smoothly into the natural flow.

    The optional prop pinnedSelectedItemOffset allows you to define the pin location for the selected item when the SelectionStrategy is pinned.

    The pinnedSelectedItemOffset prop determines where the selected item stays fixed (or 'pinned') in the Carousel's viewport. You can specify this position in two ways:

    • As a percentage (0-100%) of the viewport's size:
      • For vertical Carousels: percentage of height
      • For horizontal Carousels: percentage of width
    • Using preset values:
      • start (equals 0%)
      • center (equals 50%)
      • end (equals 100%)

    In horizontal mode, measurements are from the left edge, except for right-to-left languages.

    Variable Scroll Speed

    The Carousel component introduces a novel feature for controlling scroll speed through several AnimationDurationProps. Unlike existing components, such as Flatlist or Flashlist, this feature offers enhanced flexibility, so you can fine-tune the scrolling experience.

    The itemPressedDuration prop controls the animation duration when pressing on a Carousel item. The itemScrollDuration prop controls the animation duration used to scroll to each Carousel item. The containerSelectionChangeDuration prop controls the animation duration when changing the selected Carousel container.

    Selection Border

    The Carousel component supports displaying a border around a UI item when selected. You can style the border and choose how to draw the content and border for the selected item. You can enable the border by defining the selectionBorder prop. The default props for the selection border are shown in the following example.

    
    {
       borderStrategy: 'outset',
       borderColor: 'white',
       borderWidth: 8,
       borderRadius: 8,
       borderStrokeWidth: 3,
       borderStrokeRadius: 4,
       borderStrokeColor: 'black',
    }
    
    

    Carousel 3.0.0 supports the borderStrategy prop. The prop supports two types of strategies:

    • outset - where the border is drawn outside of the original bounds of the item, so the item’s content size remains the same but the overall item size gets bigger.
    • inset - where the border is drawn within the bounds of the item over the item's content, so while the item's content size remains the same, part of the item's content is cropped by the border. The overall item size remains the same.

    Props

    Prop Type Default Required Details
    dataAdapter CarouselItemDataAdapter<ItemT, KeyT> - TRUE The object used to retrieve carousel items and other information about them.
    renderItem (info: CarouselRenderInfo) => React.ReactElement null TRUE Method to render the carousel item.
    testID string undefined FALSE An unique identifer to locate this carousel in end-to-end tests.
    uniqueId string undefined FALSE An arbitrary and unique identifier for this carousel that will be used to determine when to recycle the carousel and its items. Commonly used by horizontal carousels within a vertical carousel (for example, nested carousels).
    orientation horizontal, vertical horizontal FALSE Specifies the direction and layout for rendering items in a carousel.
    renderedItemsCount number 8 FALSE The number of items to render at a given time when recycling through the carousel for performance optimization.
    numOffsetItems number 2 FALSE Number of items to keep to the top/left of the carousel before recycling the item component to the end.
    navigableScrollAreaMargin number 100 FALSE The margin space (in density-independent pixels) on either side of the carousel's navigable content area in the direction of the carousel's orientation (for example, left and right for horizontal carousels, top and bottom for vertical carousels).
    hasPreferredFocus boolean FALSE FALSE Determines whether this component should automatically receive focus when rendered.
    initialStartIndex number 0 FALSE Indicates the first item index in the carousel to be selected.
    hideItemsBeforeSelection boolean FALSE FALSE Determines whether items before the selected one should be hidden.
    trapSelectionOnOrientation boolean FALSE FALSE This flag will prevent selection from progressing to the nearest component outside the carousel alongside its orientation. If the carousel is horizontal and the user is at the start item and presses to move backward, or at the end item and presses to move forward, this flag will prevent selection from escaping the carousel.
    containerStyle StyleProp<ViewStyle> undefined FALSE Style applied to the carousel container.
    itemStyle CarouselItemStyleProps - FALSE Style applied to the carousel items.
    animationDuration AnimationDurationProps - FALSE The durations of various animations related to the carousel and its items.
    selectionStrategy anchored, natural, pinned anchored FALSE Specifies how the selected item moves in the carousel in response to the D-pad key presses.
    anchored - Causes the selected item to stay anchored on the initial item’s position when scrolling in either direction along the orientation.
    natural - Causes the selected item to float along the carousel's orientation until it reaches either end of the list.
    pinned - Enables the selected item to follow natural scrolling behavior at the start and end of the list, while keeping it pinned to a specific position defined by the pinnedSelectedItemOffset prop during the rest of the scrolling.
    pinnedSelectedItemOffset Percentage start, center, end 0% FALSE The value determines the relative position from the leading edge of the carousel where the selected item is pinned.
    onSelectionChanged (event: CarouselSelectionChangeEvent) => void undefined FALSE Function called whenever the current selected item on the list changes (it's not called when the carousel is selected or unselected).
    selectionBorder SelectionBorderProps undefined FALSE When this value is not undefined, the selected item will have a style-able border enveloping the selected item. All the style related props in selectionBorder property defines the look-and-feel of the border surrounding the selected item when it is enabled.
    ref React.Ref<ShovelerRef<Key>> undefined FALSE Ref to access the Carousel component's ScrollTo and EnableDPad methods.

    Selection border props

    Prop Type Default Required Details
    borderStrategy inset, outset outset FALSE Specifies how the content and the border are drawn for the selected item.
    borderWidth number 8 FALSE Specifies the thickness of the border around the selected item.
    borderColor string white FALSE Specifies the color of the border around the selected item.
    borderRadius number 8 FALSE Determines the corner radius of the border for rounded edges.
    borderStrokeWidth number 3 FALSE Specifies the thickness of the outline stroke that separates the border from the item's content.
    borderStrokeRadius number 4 FALSE Defines the corner radius for the outline stroke that separates the border from the item's content.
    borderStrokeColor string black FALSE Specifies the color of the outline stroke for the selection border.

    Animation duration props

    Prop Type Default Required Details
    itemPressedDuration number 0.15 FALSE Amount of time, in seconds, used when pressing on an item.
    itemScrollDuration number 0.2 FALSE Amount of time, in seconds, used to scroll each item.
    containerSelectionChangeDuration number itemPressedDuration FALSE Amount of time, in seconds, used for changing the selected container.
    Prop Type Default Required Details
    itemPadding number 20 FALSE The space between the adjacent items along the carousel's orientation, in pixels.
    itemPaddingOnSelection number itemPadding FALSE The space between the adjacent items along the carousel's orientation, in pixels when the carousel is selected.
    pressedItemScaleFactor number 0.8 FALSE A scale multiplier to be applied to the item when it is pressed by the user.
    selectedItemScaleFactor number 1 FALSE A scale multiplier to be applied to the item when it is selected. When this value is 1.0, it means there will be no scaling happening to the selected item while the user scrolls through the list.
    getSelectedItemOffset (info: CarouselRenderInfo) => ShiftFactor undefined FALSE A function to retrieve the offset value to be applied for an item relative to its current position.
    Prop Type Default Required Details
    getItem (index: number) => ItemT undefined TRUE A function that will receive an index, and return an item's data object. The object returned will be used to call other data-access functions to retrieve information about specific items.
    getItemCount () => number; - TRUE A function that will return the item count for the list.
    getItemKey (info: CarouselRenderInfo) => KeyT undefined TRUE Function to provide a unique key for each item based on the data and index.
    notifyDataError (error: CarouselDataError) => boolean; - TRUE This function is called by the list component when an item cannot be used in the list.

    The Carousel supports following methods through its ShovelerRef<KeyT>:

    Prop Type Default Required Details
    scrollTo (index: number, animated : boolean) : void; - FALSE Method to scroll to give Indexed Item on the Carousel
    scrollToKey (key: Key, animated: boolean) : void; - FALSE Method to scroll to the item belonging to a unique key in the Carousel
    enableDpad (enable: boolean) : void; - FALSE Support HW Key events on the Carousel

    Troubleshooting

    Vertical scroll only works with a set height

    In the case of vertical scrolling (specifically natural scrolling), you need to set containerStyle with a fixed height value.

      Usage

      This version of Carousel is included in @amazon-devices/kepler-ui-components ^2.0.0. Check your package.json to confirm the version is set as shown below.

      Copied to clipboard.

      "dependencies": {
          // ...
          "@amazon-devices/kepler-ui-components": "^2.0.0"
      }
      

      Import

      Copied to clipboard.

      import { Carousel } from '@amazon-devices/kepler-ui-components';
      

      Examples

      Horizontal Carousel

      Copied to clipboard.

      
      import React, {memo} from 'react';
      import {Image, Pressable, View} from 'react-native';
      import {
        Carousel,
        CarouselRenderInfo,
        ItemInfo,
      } from '@amazon-devices/kepler-ui-components';
      import {useState} from 'react';
      
      import {ItemType, ScrollableProps} from '../../Types';
      import {CAROUSEL_STYLE} from './Style';
      
      export const HorizontalScrollable = ({data}: ScrollableProps) => {
        function ItemView({item}: CarouselRenderInfo<ItemType>) {
          const [focus, setFocus] = useState<boolean>(false);
          const onFocusHandler = () => {
            setFocus(true);
          };
          const onBlurHandler = () => {
            setFocus(false);
          };
      
          return (
            <Pressable
              style={[
                CAROUSEL_STYLE.itemContainer,
                focus && CAROUSEL_STYLE.itemFocusContainer,
              ]}
              onFocus={onFocusHandler}
              onBlur={onBlurHandler}>
              <Image style={CAROUSEL_STYLE.imageContainer} source={{uri: item.url}} />
            </Pressable>
          );
        }
      
        const renderItemHandler = ({item, index}: CarouselRenderInfo<ItemType>) => {
          return <ItemView index={index} item={item} />;
        };
      
        const itemInfo: ItemInfo[] = [
          {
            view: ItemView,
            dimension: {
              width: 250,
              height: 420,
            },
          },
        ];
      
        const getItemForIndexHandler = (index: number) => ItemView;
      
        const keyProviderHandler = (item: ItemType, index: number) =>
          `${index}-${item.url}`;
      
        return (
          <View style={CAROUSEL_STYLE.container}>
            <Carousel
              data={data}
              orientation={'horizontal'}
              containerStyle={CAROUSEL_STYLE.horizontalCarouselContainerStyle}
              itemDimensions={itemInfo}
              itemPadding={20}
              renderItem={renderItemHandler}
              getItemForIndex={getItemForIndexHandler}
              keyProvider={keyProviderHandler}
              hasTVPreferredFocus
              hideItemsBeforeSelection={false}
              itemSelectionExpansion={{
                widthScale: 1.2,
                heightScale: 1.2,
              }}
              numOffsetItems={2}
              focusIndicatorType={'fixed'}
              maxToRenderPerBatch={10}
              firstItemOffset={100}
              dataStartIndex={0}
              initialStartIndex={0}
              shiftItemsOnSelection={true}
              trapFocusOnAxis={false}
              selectionBorder={{
                enabled: true,
                borderColor: '#FFFFFF',
                borderWidth: 2,
                borderRadius: 0,
                borderStrokeRadius: 0
              }}
            />
          </View>
        );
      };
      
      export const HorizontalMemoScrollable = memo(HorizontalScrollable);
      
      

      Vertical Carousel

      Copied to clipboard.

      
      import {Image, Pressable, View} from 'react-native';
      import {
        Carousel,
        CarouselRenderInfo,
        ItemInfo,
      } from '@amazon-devices/kepler-ui-components';
      import {memo, useState} from 'react';
      import React from 'react';
      import {ItemType, ScrollableProps} from '../../Types';
      import {CAROUSEL_STYLE} from './Style';
      
      export const VerticalScrollable = ({data}: ScrollableProps) => {
        function ItemView({item}: CarouselRenderInfo<ItemType>) {
          const [focus, setFocus] = useState<boolean>(false);
          const onFocusHandler = () => {
            setFocus(true);
          };
          const onBlurHandler = () => {
            setFocus(false);
          };
      
          return (
            <Pressable
              style={[
                CAROUSEL_STYLE.itemContainer,
                focus && CAROUSEL_STYLE.itemFocusContainer,
              ]}
              onFocus={onFocusHandler}
              onBlur={onBlurHandler}>
              <Image style={CAROUSEL_STYLE.imageContainer} source={{uri: item.url}} />
            </Pressable>
          );
        }
      
        const renderItemHandler = ({item, index}: CarouselRenderInfo<ItemType>) => {
          return <ItemView index={index} item={item} />;
        };
      
        const itemInfo: ItemInfo[] = [
          {
            view: ItemView,
            dimension: {
              width: 250,
              height: 420,
            },
          },
        ];
        
         const getItemForIndexHandler = (index: number) => ItemView;
      
        const keyProviderHandler = (item: ItemType, index: number) =>
          `${index}-${item.url}`;
      
        return (
          <View style={[CAROUSEL_STYLE.container]}>
            <Carousel
              containerStyle={CAROUSEL_STYLE.verticalCarouselContainerStyle}
              data={data}
              orientation={'vertical'}
              itemDimensions={itemInfo}
              itemPadding={10}
              renderItem={renderItemHandler}
              getItemForIndex={getItemForIndexHandler}
              keyProvider={keyProviderHandler}
              hasTVPreferredFocus
              hideItemsBeforeSelection={false}
              itemSelectionExpansion={{
                widthScale: 1.2,
                heightScale: 1.2,
              }}
              focusIndicatorType="fixed"
              numOffsetItems={2}
              maxToRenderPerBatch={11}
              itemScrollDelay={0.2}
            />
          </View>
        );
      };
      
      export const VerticalMemoScrollable = memo(VerticalScrollable);
      
      

      Heterogeneous Carousel

      Copied to clipboard.

      
      import {Image, Pressable} from 'react-native';
      import {
        Carousel,
        CarouselRenderInfo,
        ItemInfo,
      } from '@amazon-devices/kepler-ui-components';
      import {useCallback, useState} from 'react';
      import React from 'react';
      import {ItemType, ScrollableProps} from '../../Types';
      import {CAROUSEL_STYLE} from './Style';
      
      export const HeterogeneousItemViewScrollable = ({data}: ScrollableProps) => {
      
        function ItemViewType1({item}: CarouselRenderInfo<ItemType>) {
          const [focus, setFocus] = useState<boolean>(false);
      
          const onFocusHandler = useCallback(() => setFocus(true), []);
          const onBlurHandler = useCallback(() => setFocus(false), []);
      
          return (
            <Pressable
              style={[
                CAROUSEL_STYLE.itemContainerType1,
                focus && CAROUSEL_STYLE.itemFocusContainer,
              ]}
              onFocus={onFocusHandler}
              onBlur={onBlurHandler}>
              <Image style={CAROUSEL_STYLE.imageContainer} source={{uri: item.url}} />
            </Pressable>
          );
        }
      
        function ItemViewType2({item}: CarouselRenderInfo<ItemType>) {
          const [focus, setFocus] = useState<boolean>(false);
      
          const onFocusHandler = useCallback(() => setFocus(true), []);
          const onBlurHandler = useCallback(() => setFocus(false), []);
          return (
            <Pressable
              style={[
                CAROUSEL_STYLE.itemContainerType2,
                focus && CAROUSEL_STYLE.itemFocusContainer,
              ]}
              onFocus={onFocusHandler}
              onBlur={onBlurHandler}>
              <Image
                style={CAROUSEL_STYLE.imageContainer}
                resizeMode="cover"
                source={{uri: item.url}}
              />
            </Pressable>
          );
        }
      
        const renderItemHandler = ({item, index}: CarouselRenderInfo<ItemType>) => {
          return index % 2 === 0 ? (
            <ItemViewType1 index={index} item={item} />
          ) : (
            <ItemViewType2 index={index} item={item} />
          );
        };
      
        const itemInfo: ItemInfo[] = [
          {
            view: ItemViewType1,
            dimension: {
              width: CAROUSEL_STYLE.itemContainerType1.width,
              height: CAROUSEL_STYLE.itemContainerType1.height,
            },
          },
          {
            view: ItemViewType2,
            dimension: {
              width: CAROUSEL_STYLE.itemContainerType2.width,
              height: CAROUSEL_STYLE.itemContainerType2.height,
            },
          },
        ];
      
        const getItemForIndexHandler = (index: number) => {
          return index % 2 === 0 ? ItemViewType1 : ItemViewType2;
        };
      
        const keyProviderHandler = (item: ItemType, index: number) =>
          `${index}-${item.url}`;
      
        return (
          <Carousel
            data={data}
            containerStyle={CAROUSEL_STYLE.horizontalCarouselContainerStyle}
            orientation={'horizontal'}
            itemDimensions={itemInfo}
            itemPadding={40}
            renderItem={renderItemHandler}
            getItemForIndex={getItemForIndexHandler}
            keyProvider={keyProviderHandler}
            hasTVPreferredFocus
            hideItemsBeforeSelection={false}
            itemSelectionExpansion={{
              widthScale: 1.1,
              heightScale: 1.1,
            }}
            focusIndicatorType="fixed"
            numOffsetItems={3}
            maxToRenderPerBatch={11}
            firstItemOffset={100}
          />
        );
      };
      
      

      Features

      FocusIndicator

      The FocusIndicator style specifies how the focus indicator moves in a list of items in response to the D-Pad left and right key presses.

      Natural

      The natural style causes the focus indicator to float in the scroll direction until it reaches the start or end of the list.

      Fixed

      The fixed style causes the focus indicator to stay locked on the initial item’s position. When scrolling, the items reposition so that the focused item remains fixed.

      Pinned focus style

      The pinned focus style pins the focused item at a position during scrolling for larger sets of items. As the user approaches the beginning or end of the list, the scroll behavior transitions smoothly into the natural flow.

      The optional prop pinnedFocusOffset allows users to define the pin location for the selected item and the FocusIndicatorType includes the focus style PINNED.

      The pinnedFocusOffset ranges from 0% to 100%. This is the percentage of the size of the viewport. The height is used for vertical carousels and width is used for horizontal carousels. It defines the position where the selected item should be pinned relative to the start (in horizontal mode, except for languages written from right to left) or the top edge (in vertical mode) of the viewport.

      Prop Type Default Required Details
      focusIndicatorType focusIndicatorType = fixed | natural | pinned fixed FALSE Specifies how the focus indicator moves in a list of items in response to the DPad Left and Right key presses.

      fixed - focus is fixed on the far left position of the item.

      natural - focus floats in the direction of the scroll.

      pinned - focus behavior mimics natural scrolling at the beginning and end of the list. As the user scrolls through the items, the focused item is pinned to a specific position along the scrolling axis, determined by the pinnedFocusOffset.
      pinnedFocusOffset Percentage | undefined undefined FALSE The pinnedFocusOffset value determines the relative position, in percentage, from the leading edge of the Carousel where the focus of the selected or highlighted item is pinned. This value is a string Percentage type, for example, "50%".

      Note: When focusIndicatorType is set to pinned and pinnedFocusOffset is not defined, the Carousel defaults to the 'natural' focusIndicatorType behavior.

      Demo videos

      The following videos shows the Pinned focus style in horizontal and vertical scrolling.

      Horizontal scrolling

      Vertical scrolling

      Recycling

      The recycling technique involves reusing existing list item views to display new data as the user scrolls, rather than creating new views for each item. This reduces memory usage and optimizes rendering.

      The Recycling View does not allocate an item view for every item in your data source. Instead, it allocates only the number of item views that fit on the screen and it reuses those item layouts as the user scrolls. When the view first scrolls out of sight, it goes through the recycling process, shown in the following diagram with detailed steps below.

      1. When a view scrolls out of sight and is no longer displayed, it becomes a Scrap View.
      2. The Recycler has a Recycle View Heap caching system for these views.
        1. When a new item is to be displayed, a view is taken from the recycle pool for reuse. Because this view must be re-bound by the adapter before being displayed, it is called a dirty view.
        2. The dirty view is recycled: the adapter locates the data for the next item to be displayed and copies this data to the views for this item. References for these views are retrieved from the recycler view’s view holder.
        3. The recycled view is added to the list of items in the Carousel that is about to go on-screen.
      3. The recycled view goes on-screen as the user scrolls the Carousel to the next item in the list. Meanwhile, another view scrolls out of sight and is recycled according to the above steps.

      Variable Scroll speed

      The Carousel component introduces a novel feature for controlling scroll speed through scrollDuration prop. Unlike existing components such as Flatlist or Flashlist, this feature offers enhanced flexibility, empowering end-users to finely tune their scrolling experience to suit their preferences.

      The scrollableFocusScaleDuration prop controls the animation duration for item selection change when focus enters or exists the scrollable area. Adjust this value as needed for smoother transitions.

      The itemScrollDelay is in effect when the tiles are transitioning, such as when they move. If only the focus is moving then itemScrollDelay does not apply.

      Props

      Prop Type Default Required Details
      testID string kepler-shoveler FALSE An unique identifer to locate this scrollable in end-to-end tests.
      Data Item[] - TRUE Data is a plain array of items of a given type.
      itemPadding number - TRUE The space between the adjacent items in scroll direction, in pixels.
      orientation Orientation = Horizontal | Vertical horizontal FALSE The direction for rendering the scrollable items.
      itemDimensions ItemInfo[] - TRUE Contains all the Views and their sizes which will be used to render the children of this scrollable.
      maxToRenderPerBatch number 8 FALSE Maximum numbers of items to render at a time when recycling through the scrollable.
      numOffsetItems number 2 FALSE Number of items to keep to the top/left of the scrollable before recycling the component to the end.
      firstItemOffset number 0 FALSE Amount of top/left padding to put on the scrollable.
      itemSelectionExpansion Dimension = { width: number; height: number } (0,0) FALSE Space to allocate for the selected item depending on the item's dimension
      shiftItemsOnSelection boolean TRUE FALSE This flag will determine if items should be shifted in a row as selection is shifted.
      focusIndicatorType FocusStyle = fixed | natural | pinned fixed FALSE Specifies how the focus indicator moves in a list of items in response to the DPad Left and Right key presses.

      fixed - focus is fixed on the far left position of the item.

      natural - focus floats in the direction of the scroll.

      pinned - focus behavior mimics natural scrolling at the beginning and end of the list. As the user scrolls through the items, the focused item is pinned to a specific position along the scrolling axis, determined by the pinnedFocusOffset.
      pinnedFocusOffset Percentage | undefined undefined FALSE The pinnedFocusOffset value determines the relative position, in percentage, from the leading edge of the Carousel where the focus of the selected or highlighted item is pinned. This value is a string Percentage type, for example, "50%".

      Note: When focusIndicatorType is set to pinned and pinnedFocusOffset is not defined, the Carousel defaults to the 'natural' focusIndicatorType behavior.
      hasTVPreferredFocus boolean FALSE FALSE Whether the scrollable should try to obtain initial focus.
      initialStartIndex number 0 FALSE Initial focused selected index for the scrollable
      dataStartIndex number 0 FALSE Indicates the first data index that is available to the scrollable.
      hideItemsBeforeSelection boolean   FALSE If set to true, then all items before the pivot/selected items will be hidden
      itemScrollDelay number 0.2 FALSE Amount of time, in seconds, used to scroll each item
      trapFocusOnAxis boolean FALSE FALSE This flag will prevent the focus from progressing to the nearest component outside the scrollable alongside its axis. Meaning, if the scrollable is horizontal and the user is at the first item and presses left, or last item and presses right, this flag will prevent the focus from escaping the scrollable.
      containerStyle StyleProp<ViewStyle> undefined FALSE View style for the shovler container
      selectionBorder SelectionBorderProps { enabled: false,
      borderColor: 'white',
      borderWidth: 8,
      borderRadius: 8,
      borderStrokeRadius: 4,
      borderStrokeColor: 'black',
      borderStrokeWidth: 3, }
      FALSE When this flag is set to true, the selected item has a style-able border enveloping the selected item.
      ref React.Ref<ShovelerRef<Key>> undefined FALSE Ref to access the Carousel component's ScrollTo and EnableDPad methods.
      Prop function signature Default isRequired Details
      renderItem (info: ShovlerRenderInfo) => React.ReactElement | null - TRUE Method to render the scrollable Item.
      getItemForIndex (index: number) => React.FC | undefined</code> - TRUE Either a constant step size for all the elements or a method to provide the step size per item index.
      keyProvider (data: ItemT, index: number) => keyT - TRUE provider used to extract a key from an object

      The Carousel supports following methods through its ShovelerRef<KeyT>:

      Prop function signature Default Details
      scrollTo (index: number, animated : boolean) : void; - Method to scroll to give Indexed Item on the Carousel
      enableDpad (enable: boolean) : void; - Support HW Key events on the Carousel
      
      type Dimension = {  width: number; height: number }
      type ItemInfo<Prop = any> = {
          view: React.FC<Prop>;
          dimension: Dimension
      }
      
      

      Customizations

      • Variable Scroll Speed - The scroll speed between each item on the carousel can be modified using the itemScrollDelay prop, which defaults to 0.2 seconds.
      • Focus Indicator Style - The caursel supports two types of focus indicators:
        • Natural - Causes the focus indicator to float in the scroll direction until it reaches the start/end of the list
        • Fixed - Causes the focus indicator stays locked on the initial item’s position, and when scrolling the items re-position so that the focused item remains fixed.
      • Item Scale on selection - The selected or focused item on the carousel can expand in height and width by a specified factor compared to its original size.
      • Show/Hide Items before selection - Allows the option to hide or show items preceding the selected one during fixed scrolling. Displaying an item before the selected one provides a visual cue to the user that more items are available to the left.
      • Container Style - Allows setting the style for the entire carousel view. Common styles include background color, opacity, width, and height.
      • Selection Border - The Carousel component now supports displaying a border around a UI item when focused. This change introduces new props to configure the border style and a boolean that allows you to choose between using the native border or providing a custom one. Common use cases for selection border include:

        • Default border style with selctionBorder.enabled as true.

                  
            selectionBorder={{
              enabled: true
            }}
                  
          
          Selection border set to true
        • Default border style with selectionBorder.enabled as false.

                  
            selectionBorder={{
              enabled: false
            }}
                  
          
          Selection border set to false
        • Border style with borderProps and borderStrokeProps.

                  
           selectionBorder={{
               enabled: true,
               borderColor: 'white',
               borderWidth: 4,
               borderRadius: 4,
               borderStrokeRadius: 0,
               borderStrokeColor: 'transparent',
               borderStrokeWidth: 0
           }}
                  
          
          Selection border with border prop styles

      Troubleshooting

      getItemForIndex is not a function

      You may encounter an error like this example below.

      TypeError: getItemForIndex is not a function (it is undefined)
      This error is located at:
          in ItemRenderer
          in KeplerScrollable
          in KeplerScrollableWithRef
          in Scrollable (created by App)
          in RCTView (created by View)
          in View (created by App)
          ...
      

      This error is due to the changes to Carousel in the 2.0.0 version of VUIC. The enhancements required changes to the input props and behavior of the component.

      Verify which version of the package you are using. Go to your app's package.json file and find the "@amazon-devices/kepler-ui-components" dependency.

      If the package version is 2.0.0 or higher, then you have the latest version of Carousel. You have two options to resolve the error: use the new version and update your implementation, or revert to the older version.

      Option 1: Update your implementation

      If you want to continue using VUIC version 2.0.0, you need to update your implementation of the Carousel component. See Migrate to the latest version above for more information.

      Option 2: Revert to the previous Carousel version

      If you want to continue with the previous version of Carousel, change the version of "@amazon-devices/kepler-ui-components" to ^1.0.0 in your package.json file.

      Vertical scroll only works with a set height

      In the case of vertical scrolling (specifically natural scrolling), you need to set containerStyle with a fixed height value.

      When setting shiftItemsOnSelection to false, an unintended overlapping behavior can be observed

      If you set shiftItemsOnSelection to false and do not set the correct height and width, an overlapping behavior can be observed in the carousel. You need to set the correct height and width to troubleshoot this issue. The shiftItemsOnSelection prop will let the carousel component make additional room while its selected to expand itself. If you set this to false, the carousel doesn't expand.

      ItemDimension should be equal to or greater than the Card’s dimensions

      If you do not set the itemDimension equal to or greater than the card dimensions, alignment may not happen properly.


      Last updated: Oct 24, 2025