carousel

Accessible drag and snap carousel powered by Embla

Use a required content viewport and track around authored slide items. The TypeScript directive initializes the locally bundled Embla engine, synchronizes controls and accessible state, and supports pointer dragging and keyboard navigation.

<section
  ng-carousel
  align="start"
  style="--carousel-item-size: 50%; --carousel-gap: 0.5rem"
>
  <div>
    <ul>
      <li>Slide</li>
    </ul>
  </div>
</section>

Use orientation="vertical" for vertical movement and dir="rtl" for RTL. --carousel-item-size controls single or multi-item layouts and --carousel-gap controls spacing. Add autoplay and optionally autoplay-delay="2000" for the same locally bundled plugin behavior as the checked-in reference. The root mirrors snap index, snap count, item count, and boundary state.

angularcss:carousel-ready and angularcss:carousel-change expose the Embla API, zero-based snap index, snap count, selected item index, selected item, and total item count. Bind those events with AngularTS when the application needs a counter; AngularCSS does not create or replace an AngularTS model.

Example

View source
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>AngularCSS Carousel</title>
    <link rel="stylesheet" href="../../css/preflight.css" />
    <link rel="stylesheet" href="../../css/angular.css" />
    <link rel="stylesheet" href="../example.css" />
    <script src="../../js/angular-ts.umd.js"></script>
    <script src="../../js/angular-css.umd.js"></script>
  </head>
  <body ng-app="angular.css" ng-cloak data-example="carousel-demo">
    <main class="visual-example">
      <section ng-carousel class="carousel-demo" aria-label="Numbered slides">
        <div>
          <ul>
            <li>
              <div class="carousel-item-padding">
                <article class="card">
                  <section class="carousel-square">
                    <span>1</span>
                  </section>
                </article>
              </div>
            </li>
            <li>
              <div class="carousel-item-padding">
                <article class="card">
                  <section class="carousel-square">
                    <span>2</span>
                  </section>
                </article>
              </div>
            </li>
            <li>
              <div class="carousel-item-padding">
                <article class="card">
                  <section class="carousel-square">
                    <span>3</span>
                  </section>
                </article>
              </div>
            </li>
            <li>
              <div class="carousel-item-padding">
                <article class="card">
                  <section class="carousel-square">
                    <span>4</span>
                  </section>
                </article>
              </div>
            </li>
            <li>
              <div class="carousel-item-padding">
                <article class="card">
                  <section class="carousel-square">
                    <span>5</span>
                  </section>
                </article>
              </div>
            </li>
          </ul>
        </div>
        <button
          size="icon-sm"
          variant="outline"
          type="button"
          aria-label="Previous slide"
        >
          <svg
            viewBox="0 0 24 24"
            fill="none"
            stroke="currentColor"
            stroke-width="2"
            aria-hidden="true"
          >
            <path d="m15 18-6-6 6-6" />
          </svg>
        </button>
        <button
          size="icon-sm"
          variant="outline"
          type="button"
          aria-label="Next slide"
        >
          <svg
            viewBox="0 0 24 24"
            fill="none"
            stroke="currentColor"
            stroke-width="2"
            aria-hidden="true"
          >
            <path d="m9 18 6-6-6-6" />
          </svg>
        </button>
      </section>
    </main>
  </body>
</html>

Behavior workflows

The following functional page covers API state, multiple visible items, vertical orientation, and autoplay.

View source
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>AngularCSS Carousel Workflows</title>
    <link rel="stylesheet" href="../../css/preflight.css" />
    <link rel="stylesheet" href="../../css/angular.css" />
    <link rel="stylesheet" href="../example.css" />
    <script src="../../js/angular-ts.umd.js"></script>
    <script src="../../js/angular-css.umd.js"></script>
  </head>
  <body
    ng-app="angular.css"
    ng-cloak
    ng-init="apiState={index:0,count:5}; autoplayState={index:0,count:5}"
    data-example="carousel-api carousel-multiple carousel-orientation carousel-plugin"
  >
    <main
      class="carousel-workflow-grid"
      aria-label="Carousel behavior workflows"
    >
      <section class="carousel-workflow" aria-labelledby="carousel-api-heading">
        <h2 id="carousel-api-heading">API</h2>
        <section
          ng-carousel
          class="carousel-api-demo"
          aria-label="API numbered slides"
          ng-on-angularcss:carousel-ready="apiState=$event.detail"
          ng-on-angularcss:carousel-change="apiState=$event.detail"
        >
          <div>
            <ul>
              <li>
                <article class="card">
                  <section class="carousel-square">
                    <span>1</span>
                  </section>
                </article>
              </li>
              <li>
                <article class="card">
                  <section class="carousel-square">
                    <span>2</span>
                  </section>
                </article>
              </li>
              <li>
                <article class="card">
                  <section class="carousel-square">
                    <span>3</span>
                  </section>
                </article>
              </li>
              <li>
                <article class="card">
                  <section class="carousel-square">
                    <span>4</span>
                  </section>
                </article>
              </li>
              <li>
                <article class="card">
                  <section class="carousel-square">
                    <span>5</span>
                  </section>
                </article>
              </li>
            </ul>
          </div>
          <button
            size="icon-sm"
            variant="outline"
            type="button"
            aria-label="Previous API slide"
          >
            <svg
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              stroke-width="2"
              aria-hidden="true"
            >
              <path d="m15 18-6-6 6-6" />
            </svg>
          </button>
          <button
            size="icon-sm"
            variant="outline"
            type="button"
            aria-label="Next API slide"
          >
            <svg
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              stroke-width="2"
              aria-hidden="true"
            >
              <path d="m9 18 6-6-6-6" />
            </svg>
          </button>
        </section>
        <output class="carousel-status" aria-live="polite">
          Slide {{ apiState.index + 1 }} of {{ apiState.count }}
        </output>
      </section>

      <section
        class="carousel-workflow"
        aria-labelledby="carousel-multiple-heading"
      >
        <h2 id="carousel-multiple-heading">Multiple items</h2>
        <section
          ng-carousel
          align="start"
          class="carousel-multiple-demo"
          aria-label="Multiple numbered slides"
        >
          <div>
            <ul>
              <li>
                <div class="carousel-item-padding">
                  <article class="card">
                    <section class="carousel-square carousel-square-medium">
                      <span>1</span>
                    </section>
                  </article>
                </div>
              </li>
              <li>
                <div class="carousel-item-padding">
                  <article class="card">
                    <section class="carousel-square carousel-square-medium">
                      <span>2</span>
                    </section>
                  </article>
                </div>
              </li>
              <li>
                <div class="carousel-item-padding">
                  <article class="card">
                    <section class="carousel-square carousel-square-medium">
                      <span>3</span>
                    </section>
                  </article>
                </div>
              </li>
              <li>
                <div class="carousel-item-padding">
                  <article class="card">
                    <section class="carousel-square carousel-square-medium">
                      <span>4</span>
                    </section>
                  </article>
                </div>
              </li>
              <li>
                <div class="carousel-item-padding">
                  <article class="card">
                    <section class="carousel-square carousel-square-medium">
                      <span>5</span>
                    </section>
                  </article>
                </div>
              </li>
            </ul>
          </div>
          <button
            size="icon-sm"
            variant="outline"
            type="button"
            aria-label="Previous group"
          >
            <svg
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              stroke-width="2"
              aria-hidden="true"
            >
              <path d="m15 18-6-6 6-6" />
            </svg>
          </button>
          <button
            size="icon-sm"
            variant="outline"
            type="button"
            aria-label="Next group"
          >
            <svg
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              stroke-width="2"
              aria-hidden="true"
            >
              <path d="m9 18 6-6-6-6" />
            </svg>
          </button>
        </section>
      </section>

      <section
        class="carousel-workflow"
        aria-labelledby="carousel-orientation-heading"
      >
        <h2 id="carousel-orientation-heading">Vertical orientation</h2>
        <section
          ng-carousel
          orientation="vertical"
          align="start"
          class="carousel-vertical-demo"
          aria-label="Vertical numbered slides"
        >
          <div>
            <ul>
              <li>
                <div class="carousel-item-padding">
                  <article class="card">
                    <section class="carousel-vertical-card">
                      <span>1</span>
                    </section>
                  </article>
                </div>
              </li>
              <li>
                <div class="carousel-item-padding">
                  <article class="card">
                    <section class="carousel-vertical-card">
                      <span>2</span>
                    </section>
                  </article>
                </div>
              </li>
              <li>
                <div class="carousel-item-padding">
                  <article class="card">
                    <section class="carousel-vertical-card">
                      <span>3</span>
                    </section>
                  </article>
                </div>
              </li>
              <li>
                <div class="carousel-item-padding">
                  <article class="card">
                    <section class="carousel-vertical-card">
                      <span>4</span>
                    </section>
                  </article>
                </div>
              </li>
              <li>
                <div class="carousel-item-padding">
                  <article class="card">
                    <section class="carousel-vertical-card">
                      <span>5</span>
                    </section>
                  </article>
                </div>
              </li>
            </ul>
          </div>
          <button
            size="icon-sm"
            variant="outline"
            type="button"
            aria-label="Previous vertical slide"
          >
            <svg
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              stroke-width="2"
              aria-hidden="true"
            >
              <path d="m15 18-6-6 6-6" />
            </svg>
          </button>
          <button
            size="icon-sm"
            variant="outline"
            type="button"
            aria-label="Next vertical slide"
          >
            <svg
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              stroke-width="2"
              aria-hidden="true"
            >
              <path d="m9 18 6-6-6-6" />
            </svg>
          </button>
        </section>
      </section>

      <section
        class="carousel-workflow"
        aria-labelledby="carousel-plugin-heading"
      >
        <h2 id="carousel-plugin-heading">Autoplay plugin</h2>
        <section
          ng-carousel
          autoplay
          autoplay-delay="2000"
          class="carousel-api-demo"
          aria-label="Autoplay numbered slides"
          ng-on-angularcss:carousel-ready="autoplayState=$event.detail"
          ng-on-angularcss:carousel-change="autoplayState=$event.detail"
        >
          <div>
            <ul>
              <li>
                <div class="carousel-item-padding">
                  <article class="card">
                    <section class="carousel-square">
                      <span>1</span>
                    </section>
                  </article>
                </div>
              </li>
              <li>
                <div class="carousel-item-padding">
                  <article class="card">
                    <section class="carousel-square">
                      <span>2</span>
                    </section>
                  </article>
                </div>
              </li>
              <li>
                <div class="carousel-item-padding">
                  <article class="card">
                    <section class="carousel-square">
                      <span>3</span>
                    </section>
                  </article>
                </div>
              </li>
              <li>
                <div class="carousel-item-padding">
                  <article class="card">
                    <section class="carousel-square">
                      <span>4</span>
                    </section>
                  </article>
                </div>
              </li>
              <li>
                <div class="carousel-item-padding">
                  <article class="card">
                    <section class="carousel-square">
                      <span>5</span>
                    </section>
                  </article>
                </div>
              </li>
            </ul>
          </div>
          <button
            size="icon-sm"
            variant="outline"
            type="button"
            aria-label="Previous autoplay slide"
          >
            <svg
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              stroke-width="2"
              aria-hidden="true"
            >
              <path d="m15 18-6-6 6-6" />
            </svg>
          </button>
          <button
            size="icon-sm"
            variant="outline"
            type="button"
            aria-label="Next autoplay slide"
          >
            <svg
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              stroke-width="2"
              aria-hidden="true"
            >
              <path d="m9 18 6-6-6-6" />
            </svg>
          </button>
        </section>
        <output class="carousel-status" aria-live="polite">
          Autoplay slide {{ autoplayState.index + 1 }} of {{ autoplayState.count
          }}
        </output>
      </section>
    </main>
  </body>
</html>

Layout workflows

RTL direction, responsive item sizing, and custom spacing remain semantic HTML and authored CSS composition.

View source
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>AngularCSS Carousel Compositions</title>
    <link rel="stylesheet" href="../../css/preflight.css" />
    <link rel="stylesheet" href="../../css/angular.css" />
    <link rel="stylesheet" href="../example.css" />
    <script src="../../js/angular-ts.umd.js"></script>
    <script src="../../js/angular-css.umd.js"></script>
  </head>
  <body
    ng-app="angular.css"
    ng-cloak
    data-example="carousel-rtl carousel-size carousel-spacing"
  >
    <main
      class="carousel-composition-grid"
      aria-label="Carousel layout compositions"
    >
      <section
        class="carousel-workflow"
        aria-labelledby="carousel-rtl-heading"
        dir="rtl"
        lang="ar"
      >
        <h2 id="carousel-rtl-heading">من اليمين إلى اليسار</h2>
        <section
          ng-carousel
          dir="rtl"
          class="carousel-demo"
          aria-label="شرائح مرقمة"
        >
          <div>
            <ul>
              <li>
                <div class="carousel-item-padding">
                  <article class="card">
                    <section class="carousel-square">
                      <span>١</span>
                    </section>
                  </article>
                </div>
              </li>
              <li>
                <div class="carousel-item-padding">
                  <article class="card">
                    <section class="carousel-square">
                      <span>٢</span>
                    </section>
                  </article>
                </div>
              </li>
              <li>
                <div class="carousel-item-padding">
                  <article class="card">
                    <section class="carousel-square">
                      <span>٣</span>
                    </section>
                  </article>
                </div>
              </li>
              <li>
                <div class="carousel-item-padding">
                  <article class="card">
                    <section class="carousel-square">
                      <span>٤</span>
                    </section>
                  </article>
                </div>
              </li>
              <li>
                <div class="carousel-item-padding">
                  <article class="card">
                    <section class="carousel-square">
                      <span>٥</span>
                    </section>
                  </article>
                </div>
              </li>
            </ul>
          </div>
          <button
            size="icon-sm"
            variant="outline"
            type="button"
            aria-label="الشريحة السابقة"
          >
            <svg
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              stroke-width="2"
              aria-hidden="true"
            >
              <path d="m15 18-6-6 6-6" />
            </svg>
          </button>
          <button
            size="icon-sm"
            variant="outline"
            type="button"
            aria-label="الشريحة التالية"
          >
            <svg
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              stroke-width="2"
              aria-hidden="true"
            >
              <path d="m9 18 6-6-6-6" />
            </svg>
          </button>
        </section>
      </section>

      <section
        class="carousel-workflow"
        aria-labelledby="carousel-size-heading"
      >
        <h2 id="carousel-size-heading">Responsive size</h2>
        <section
          ng-carousel
          align="start"
          class="carousel-size-demo"
          aria-label="Sized numbered slides"
        >
          <div>
            <ul>
              <li>
                <div class="carousel-item-padding">
                  <article class="card">
                    <section class="carousel-square carousel-square-medium">
                      <span>1</span>
                    </section>
                  </article>
                </div>
              </li>
              <li>
                <div class="carousel-item-padding">
                  <article class="card">
                    <section class="carousel-square carousel-square-medium">
                      <span>2</span>
                    </section>
                  </article>
                </div>
              </li>
              <li>
                <div class="carousel-item-padding">
                  <article class="card">
                    <section class="carousel-square carousel-square-medium">
                      <span>3</span>
                    </section>
                  </article>
                </div>
              </li>
              <li>
                <div class="carousel-item-padding">
                  <article class="card">
                    <section class="carousel-square carousel-square-medium">
                      <span>4</span>
                    </section>
                  </article>
                </div>
              </li>
              <li>
                <div class="carousel-item-padding">
                  <article class="card">
                    <section class="carousel-square carousel-square-medium">
                      <span>5</span>
                    </section>
                  </article>
                </div>
              </li>
            </ul>
          </div>
          <button
            size="icon-sm"
            variant="outline"
            type="button"
            aria-label="Previous sized slide"
          >
            <svg
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              stroke-width="2"
              aria-hidden="true"
            >
              <path d="m15 18-6-6 6-6" />
            </svg>
          </button>
          <button
            size="icon-sm"
            variant="outline"
            type="button"
            aria-label="Next sized slide"
          >
            <svg
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              stroke-width="2"
              aria-hidden="true"
            >
              <path d="m9 18 6-6-6-6" />
            </svg>
          </button>
        </section>
      </section>

      <section
        class="carousel-workflow carousel-composition-wide"
        aria-labelledby="carousel-spacing-heading"
      >
        <h2 id="carousel-spacing-heading">Custom spacing</h2>
        <section
          ng-carousel
          align="start"
          class="carousel-spacing-demo"
          aria-label="Tightly spaced numbered slides"
        >
          <div>
            <ul>
              <li>
                <div class="carousel-item-padding">
                  <article class="card">
                    <section class="carousel-square carousel-square-small">
                      <span>1</span>
                    </section>
                  </article>
                </div>
              </li>
              <li>
                <div class="carousel-item-padding">
                  <article class="card">
                    <section class="carousel-square carousel-square-small">
                      <span>2</span>
                    </section>
                  </article>
                </div>
              </li>
              <li>
                <div class="carousel-item-padding">
                  <article class="card">
                    <section class="carousel-square carousel-square-small">
                      <span>3</span>
                    </section>
                  </article>
                </div>
              </li>
              <li>
                <div class="carousel-item-padding">
                  <article class="card">
                    <section class="carousel-square carousel-square-small">
                      <span>4</span>
                    </section>
                  </article>
                </div>
              </li>
              <li>
                <div class="carousel-item-padding">
                  <article class="card">
                    <section class="carousel-square carousel-square-small">
                      <span>5</span>
                    </section>
                  </article>
                </div>
              </li>
            </ul>
          </div>
          <button
            size="icon-sm"
            variant="outline"
            type="button"
            aria-label="Previous tightly spaced slide"
          >
            <svg
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              stroke-width="2"
              aria-hidden="true"
            >
              <path d="m15 18-6-6 6-6" />
            </svg>
          </button>
          <button
            size="icon-sm"
            variant="outline"
            type="button"
            aria-label="Next tightly spaced slide"
          >
            <svg
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              stroke-width="2"
              aria-hidden="true"
            >
              <path d="m9 18 6-6-6-6" />
            </svg>
          </button>
        </section>
      </section>
    </main>
  </body>
</html>

Installation

Install AngularCSS, load its stylesheet, and include the angular.css module in your AngularTS application. See Installation for the complete setup.

This component’s root directive is [ng-carousel]. Importing the package registers it with the AngularCSS angular.css module; there is no per-component JavaScript registration step.

Anatomy

Directive selectors

  • ng-carousel

Semantic structure

The content viewport and its direct track child are required. Items must be direct track children. Navigation controls and dots are optional.

API

Attributes and state

AttributeAccessPurpose
alignInputCross-axis alignment: start, center, or end.
aria-currentOutputCurrent item or date state.
aria-disabledOutputSemantic disabled state.
aria-hiddenOutputWhether generated or collapsed content is hidden from assistive technology.
aria-labelInput/outputAccessible name when visible text is insufficient.
aria-roledescriptionOutputHuman-readable description of the component role.
autoplayInputEnables the locally bundled Embla autoplay plugin.
autoplay-delayInputAutoplay delay in milliseconds.
contain-scrollInputEmbla scroll containment mode.
dirInputText and interaction direction: ltr or rtl.
drag-freeInputAllows free dragging between snap points.
draggableInputSet to false to disable pointer dragging.
loopInputAllows navigation to wrap from the final item to the first.
orientationInput/outputLayout direction: horizontal or vertical.
roleInput/outputExplicit semantic role when native HTML does not provide one.
skip-snapsInputAllows momentum to skip snap points.
slides-to-scrollInputNumber of slides advanced as one snap group.
tabindexInput/outputKeyboard focus order for composite descendants.

Input attributes are read from authored HTML. Output attributes are maintained by AngularCSS for CSS and testing. Input/output attributes may be authored for a controlled initial state and are then synchronized by the directive.

CSS custom properties

VariablePurpose
--carousel-gapGap between slides; defaults to four spacing units.
--carousel-item-sizeSlide basis; defaults to 100%.

DOM events

  • angularcss:carousel-change
  • angularcss:carousel-ready

Native DOM events continue to work normally. AngularTS event directives such as ng-click and ng-keydown, plus the data-change model callback, remain application-owned.

Behavior

The directive uses Embla to own drag gestures, snap selection, orientation, loop boundaries, control availability, and optional autoplay. AngularTS remains responsible for counters, business actions, and other application state consumed from the carousel DOM events.

AngularCSS does not replace AngularTS interpolation, bindings, structural directives, form controllers, validation, or application state.

Accessibility

The root is an accessible carousel region, every authored item is exposed as a labeled slide, and unavailable previous or next controls are disabled. Give the region a useful accessible name and keep authored slide content semantic.

Authored accessible names and relationships are preserved. Test the final composition with keyboard navigation and assistive technology because labels and content come from the application.

Customization

Target [ng-carousel], semantic descendants, component classes, and generated state with ordinary CSS. Keep behavior and accessible state in the TypeScript directive; visual choices belong in the application stylesheet.

Read Customization for layer order, design tokens, state variants, and iframe demo isolation.